diff --git a/.gitignore b/.gitignore index be148e58..eb3b04c3 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,8 @@ out # Output of the go coverage tool, specifically when used with LiteIDE *.out -# Kubernetes Generated files - skip generated files, except for vendored files - -!vendor/**/zz_generated.* +# Earthly +.tmp-earthly-out # editor and IDE paraphernalia .idea diff --git a/.golangci.yml b/.golangci.yml index 218ad899..4ecd3a03 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -6,30 +6,21 @@ linters-settings: statements: 50 gocritic: enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style + - diagnostic + - experimental + - opinionated + - performance + - style disabled-checks: - - dupImport # https://github.com/go-critic/go-critic/issues/845 - - ifElseChain - - octalLiteral - - whyNoLint - - wrapperFunc - - hugeParam + - dupImport # https://github.com/go-critic/go-critic/issues/845 + - ifElseChain + - octalLiteral + - whyNoLint + - wrapperFunc + - hugeParam gocyclo: - min-complexity: - 30 # decrease this - # gci: - # local-prefixes: insert your package name here - # goimports: - # local-prefixes: insert your package name here - #revive: - #rules: - #- name: var-naming - #severity: warning + min-complexity: 30 gomnd: settings: mnd: @@ -54,31 +45,31 @@ linters: # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint disable-all: true enable: - - bodyclose - - dogsled - - gocritic - - gofmt - - goimports - - goprintffuncname - - gosimple - - govet - - ineffassign - - lll - - misspell - - nakedret - - rowserrcheck - - staticcheck - - stylecheck - - typecheck - - unconvert - - unparam - - unused - - whitespace - - revive - - dupl - - gocyclo - - gosec - - nolintlint + - bodyclose + - dogsled + - gocritic + - gofmt + - goimports + - goprintffuncname + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - rowserrcheck + - staticcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - whitespace + - revive + - dupl + - gocyclo + - gosec + - nolintlint # don't enable: # - noctx @@ -107,25 +98,28 @@ linters: issues: # Excluding configuration per-path, per-linter, per-text and per-source exclude-rules: - - path: _test\.go - linters: - - gomnd - - gochecknoglobals - - gosec - - noctx - - goerr113 - - goconst - - dupl - - unparam + - path: _test\.go + linters: + - gomnd + - gochecknoglobals + - gosec + - noctx + - goerr113 + - goconst + - dupl + - unparam + + # https://github.com/go-critic/go-critic/issues/926 + - linters: + - gocritic + text: "unnecessaryDefer:" - # https://github.com/go-critic/go-critic/issues/926 - - linters: - - gocritic - text: "unnecessaryDefer:" + - text: "should not use dot imports" + path: _test\.go # silence stupid linter errors exclude: - - directive `// nolint.*` should be written without leading space + - directive `// nolint.*` should be written without leading space run: timeout: 15m diff --git a/charts/yawol-controller/Chart.yaml b/charts/yawol-controller/Chart.yaml index a4c2650a..6de988e3 100644 --- a/charts/yawol-controller/Chart.yaml +++ b/charts/yawol-controller/Chart.yaml @@ -3,5 +3,5 @@ description: Helm chart for yawol-controller name: yawol-controller sources: - https://github.com/stackitcloud/yawol -version: "0.24.0" -appVersion: v0.24.0 +version: "0.25.0" +appVersion: v0.25.0 diff --git a/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancermachines.yaml b/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancermachines.yaml index 144dda98..7316cdfa 100644 --- a/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancermachines.yaml +++ b/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancermachines.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.15.0 name: loadbalancermachines.yawol.stackit.cloud spec: group: yawol.stackit.cloud @@ -39,14 +39,19 @@ spec: API. 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' + 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' + 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 @@ -89,9 +94,9 @@ spec: zone for the LoadBalancer. type: string defaultNetwork: - description: DefaultNetwork defines the default/listener network - for the Loadbalancer. TODO Remove optional when Deprecations - are removed + description: |- + DefaultNetwork defines the default/listener network for the Loadbalancer. + TODO Remove optional when Deprecations are removed properties: floatingNetID: description: FloatingNetID defines an openstack ID for the @@ -101,8 +106,7 @@ spec: description: NetworkID defines an openstack ID for the network. type: string subnetID: - description: SubnetID defines an openstack ID for the - subnet. + description: SubnetID defines an openstack ID for the subnet. type: string required: - networkID @@ -124,21 +128,23 @@ spec: virtual machines. type: string flavorName: - description: NOT IMPLEMENTED ONLY FlavorID is supported. FlavorName - is the name of the flavor used for requesting virtual machines. + description: |- + NOT IMPLEMENTED ONLY FlavorID is supported. + FlavorName is the name of the flavor used for requesting virtual machines. FlavorName is only used if FlavorID is not defined. type: string flavorSearch: - description: NOT IMPLEMENTED ONLY FlavorID is supported. FlavorSearch - is a search string to find the flavor used for requesting - virtual machines. Search will be performed in metadata of - the flavors. FlavorSearch is only used if FlavorName and - FlavorID are not defined. + description: |- + NOT IMPLEMENTED ONLY FlavorID is supported. + FlavorSearch is a search string to find the flavor used for requesting virtual machines. + Search will be performed in metadata of the flavors. + FlavorSearch is only used if FlavorName and FlavorID are not defined. type: string type: object floatingNetID: - description: 'Deprecated: use defaultNetwork instead FloatingNetID - defines a openstack ID for the floatingNet.' + description: |- + Deprecated: use defaultNetwork instead + FloatingNetID defines a openstack ID for the floatingNet. type: string image: description: Image defines openstack image for the LoadBalancer. @@ -157,26 +163,28 @@ spec: machines. type: string imageName: - description: NOT IMPLEMENTED ONLY ImageID is supported. ImageName - is the name of the image used for requesting virtual machines. + description: |- + NOT IMPLEMENTED ONLY ImageID is supported. + ImageName is the name of the image used for requesting virtual machines. ImageName is only used if ImageID is not defined. type: string imageSearch: - description: NOT IMPLEMENTED ONLY ImageID is supported. ImageSearch - is a search string to find the image used for requesting - virtual machines. Search will be performed in metadata of - the images. ImageSearch is only used if ImageName and ImageID - are not defined. + description: |- + NOT IMPLEMENTED ONLY ImageID is supported. + ImageSearch is a search string to find the image used for requesting virtual machines. + Search will be performed in metadata of the images. + ImageSearch is only used if ImageName and ImageID are not defined. type: string type: object networkID: - description: 'Deprecated: use defaultNetwork instead NetworkID - defines a openstack ID for the network.' + description: |- + Deprecated: use defaultNetwork instead + NetworkID defines a openstack ID for the network. type: string projectID: - description: ProjectID defines an openstack project ID which will - be used instead of the project from the secret ref. If not set - the project from the secret ref will be used. + description: |- + ProjectID defines an openstack project ID which will be used instead of the project from the secret ref. + If not set the project from the secret ref will be used. type: string required: - authSecretRef @@ -292,8 +300,9 @@ spec: type: object type: array portID: - description: 'Deprecated: use defaultPortID instead PortID contains - the openstack port ID for a LoadBalancerMachine.' + description: |- + Deprecated: use defaultPortID instead + PortID contains the openstack port ID for a LoadBalancerMachine. type: string roleBindingName: description: RoleBindingName contains the namespacedName from the diff --git a/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancers.yaml b/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancers.yaml index 4f858936..790ffd28 100644 --- a/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancers.yaml +++ b/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.15.0 name: loadbalancers.yawol.stackit.cloud spec: group: yawol.stackit.cloud @@ -38,14 +38,19 @@ spec: description: LoadBalancer is the Schema for the YAWOL LoadBalancer API 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' + 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' + 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 @@ -120,9 +125,9 @@ spec: zone for the LoadBalancer. type: string defaultNetwork: - description: DefaultNetwork defines the default/listener network - for the Loadbalancer. TODO Remove optional when Deprecations - are removed + description: |- + DefaultNetwork defines the default/listener network for the Loadbalancer. + TODO Remove optional when Deprecations are removed properties: floatingNetID: description: FloatingNetID defines an openstack ID for the @@ -132,8 +137,7 @@ spec: description: NetworkID defines an openstack ID for the network. type: string subnetID: - description: SubnetID defines an openstack ID for the - subnet. + description: SubnetID defines an openstack ID for the subnet. type: string required: - networkID @@ -155,21 +159,23 @@ spec: virtual machines. type: string flavorName: - description: NOT IMPLEMENTED ONLY FlavorID is supported. FlavorName - is the name of the flavor used for requesting virtual machines. + description: |- + NOT IMPLEMENTED ONLY FlavorID is supported. + FlavorName is the name of the flavor used for requesting virtual machines. FlavorName is only used if FlavorID is not defined. type: string flavorSearch: - description: NOT IMPLEMENTED ONLY FlavorID is supported. FlavorSearch - is a search string to find the flavor used for requesting - virtual machines. Search will be performed in metadata of - the flavors. FlavorSearch is only used if FlavorName and - FlavorID are not defined. + description: |- + NOT IMPLEMENTED ONLY FlavorID is supported. + FlavorSearch is a search string to find the flavor used for requesting virtual machines. + Search will be performed in metadata of the flavors. + FlavorSearch is only used if FlavorName and FlavorID are not defined. type: string type: object floatingNetID: - description: 'Deprecated: use defaultNetwork instead FloatingNetID - defines a openstack ID for the floatingNet.' + description: |- + Deprecated: use defaultNetwork instead + FloatingNetID defines a openstack ID for the floatingNet. type: string image: description: Image defines openstack image for the LoadBalancer. @@ -188,26 +194,28 @@ spec: machines. type: string imageName: - description: NOT IMPLEMENTED ONLY ImageID is supported. ImageName - is the name of the image used for requesting virtual machines. + description: |- + NOT IMPLEMENTED ONLY ImageID is supported. + ImageName is the name of the image used for requesting virtual machines. ImageName is only used if ImageID is not defined. type: string imageSearch: - description: NOT IMPLEMENTED ONLY ImageID is supported. ImageSearch - is a search string to find the image used for requesting - virtual machines. Search will be performed in metadata of - the images. ImageSearch is only used if ImageName and ImageID - are not defined. + description: |- + NOT IMPLEMENTED ONLY ImageID is supported. + ImageSearch is a search string to find the image used for requesting virtual machines. + Search will be performed in metadata of the images. + ImageSearch is only used if ImageName and ImageID are not defined. type: string type: object networkID: - description: 'Deprecated: use defaultNetwork instead NetworkID - defines a openstack ID for the network.' + description: |- + Deprecated: use defaultNetwork instead + NetworkID defines a openstack ID for the network. type: string projectID: - description: ProjectID defines an openstack project ID which will - be used instead of the project from the secret ref. If not set - the project from the secret ref will be used. + description: |- + ProjectID defines an openstack project ID which will be used instead of the project from the secret ref. + If not set the project from the secret ref will be used. type: string required: - authSecretRef @@ -246,34 +254,33 @@ spec: type: string type: object serverGroupPolicy: - description: ServerGroupPolicy creates a server group with that - policy. Can be 'affinity', 'anti-affinity' 'soft-affinity', - 'soft-anti-affinity' depending on the OpenStack Infrastructure. - If empty Openstack server group will not be used. Default is - disabled + description: |- + ServerGroupPolicy creates a server group with that policy. + Can be 'affinity', 'anti-affinity' 'soft-affinity', 'soft-anti-affinity' depending on the OpenStack Infrastructure. + If empty Openstack server group will not be used. Default is disabled type: string tcpIdleTimeout: - description: TCPIdleTimeout sets TCP idle Timeout for all TCP - connections from this LoadBalancer. Value is in Seconds. With - 0 you disable the idle timeout, be careful this can lead to - side effects. Default is 1h. + description: |- + TCPIdleTimeout sets TCP idle Timeout for all TCP connections from this LoadBalancer. + Value is in Seconds. With 0 you disable the idle timeout, be careful this can lead to side effects. + Default is 1h. type: string tcpProxyProtocol: description: TCPProxyProtocol enables HAProxy TCP Proxy Protocol type: boolean tcpProxyProtocolPortFilter: - description: TCPProxyProtocolPortList enables HAProxy TCP Proxy - Protocol for specified ports. If empty it is enabled for all - ports. Only has an affect if TCPProxyProtocol is enabled. + description: |- + TCPProxyProtocolPortList enables HAProxy TCP Proxy Protocol for specified ports. + If empty it is enabled for all ports. Only has an affect if TCPProxyProtocol is enabled. items: format: int32 type: integer type: array udpIdleTimeout: - description: UDPIdleTimeout sets UDP idle Timeout for all UDP - connections from this LoadBalancer. Value is in Seconds. With - 0 you disable the idle timeout, be careful this can lead to - side effects. Default is 1m. + description: |- + UDPIdleTimeout sets UDP idle Timeout for all UDP connections from this LoadBalancer. + Value is in Seconds. With 0 you disable the idle timeout, be careful this can lead to side effects. + Default is 1m. type: string type: object ports: @@ -283,37 +290,45 @@ spec: description: ServicePort contains information on service's port. properties: appProtocol: - description: "The application protocol for this port. This is - used as a hint for implementations to offer richer behavior - for protocols that they understand. This field follows standard - Kubernetes label syntax. Valid values are either: \n * Un-prefixed - protocol names - reserved for IANA standard service names - (as per RFC-6335 and https://www.iana.org/assignments/service-names). - \n * Kubernetes-defined prefixed names: * 'kubernetes.io/h2c' - - HTTP/2 over cleartext as described in https://www.rfc-editor.org/rfc/rfc7540 - * 'kubernetes.io/ws' - WebSocket over cleartext as described - in https://www.rfc-editor.org/rfc/rfc6455 * 'kubernetes.io/wss' - - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 - \n * Other protocols should use implementation-defined prefixed - names such as mycompany.com/my-custom-protocol." + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. type: string name: - description: The name of this port within the service. This - must be a DNS_LABEL. All ports within a ServiceSpec must have - unique names. When considering the endpoints for a Service, - this must match the 'name' field in the EndpointPort. Optional - if only one ServicePort is defined on this service. + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. type: string nodePort: - description: 'The port on each node on which this service is - exposed when type is NodePort or LoadBalancer. Usually assigned - by the system. If a value is specified, in-range, and not - in use it will be used, otherwise the operation will fail. If - not specified, a port will be allocated if this Service requires - one. If this field is specified when creating a Service which - does not need it, creation will fail. This field will be wiped - when updating a Service to no longer need it (e.g. changing - type from NodePort to ClusterIP). More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport format: int32 type: integer port: @@ -322,21 +337,23 @@ spec: type: integer protocol: default: TCP - description: The IP protocol for this port. Supports "TCP", - "UDP", and "SCTP". Default is TCP. + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. type: string targetPort: anyOf: - type: integer - type: string - description: 'Number or name of the port to access on the pods - targeted by the service. Number must be in the range 1 to - 65535. Name must be an IANA_SVC_NAME. If this is a string, - it will be looked up as a named port in the target Pod''s - container ports. If this is not specified, the value of the - ''port'' field is used (an identity map). This field is ignored - for services with clusterIP=None, and should be omitted or - set equal to the ''port'' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service' + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service x-kubernetes-int-or-string: true required: - port @@ -356,41 +373,42 @@ spec: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: description: key is the label key that the selector applies to. type: string operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic merge patch. items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic diff --git a/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancersets.yaml b/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancersets.yaml index 67879fbc..ef3c5106 100644 --- a/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancersets.yaml +++ b/charts/yawol-controller/crds/yawol.stackit.cloud_loadbalancersets.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.13.0 + controller-gen.kubebuilder.io/version: v0.15.0 name: loadbalancersets.yawol.stackit.cloud spec: group: yawol.stackit.cloud @@ -38,14 +38,19 @@ spec: description: LoadBalancerSet is the Schema for the LoadBalancerSet's API. 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' + 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' + 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 @@ -66,41 +71,42 @@ spec: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: description: key is the label key that the selector applies to. type: string operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic merge patch. items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic @@ -153,9 +159,9 @@ spec: zone for the LoadBalancer. type: string defaultNetwork: - description: DefaultNetwork defines the default/listener - network for the Loadbalancer. TODO Remove optional when - Deprecations are removed + description: |- + DefaultNetwork defines the default/listener network for the Loadbalancer. + TODO Remove optional when Deprecations are removed properties: floatingNetID: description: FloatingNetID defines an openstack ID @@ -166,8 +172,8 @@ spec: the network. type: string subnetID: - description: SubnetID defines an openstack ID - for the subnet. + description: SubnetID defines an openstack ID for + the subnet. type: string required: - networkID @@ -189,23 +195,23 @@ spec: virtual machines. type: string flavorName: - description: NOT IMPLEMENTED ONLY FlavorID is supported. - FlavorName is the name of the flavor used for requesting - virtual machines. FlavorName is only used if FlavorID - is not defined. + description: |- + NOT IMPLEMENTED ONLY FlavorID is supported. + FlavorName is the name of the flavor used for requesting virtual machines. + FlavorName is only used if FlavorID is not defined. type: string flavorSearch: - description: NOT IMPLEMENTED ONLY FlavorID is supported. - FlavorSearch is a search string to find the flavor - used for requesting virtual machines. Search will - be performed in metadata of the flavors. FlavorSearch - is only used if FlavorName and FlavorID are not - defined. + description: |- + NOT IMPLEMENTED ONLY FlavorID is supported. + FlavorSearch is a search string to find the flavor used for requesting virtual machines. + Search will be performed in metadata of the flavors. + FlavorSearch is only used if FlavorName and FlavorID are not defined. type: string type: object floatingNetID: - description: 'Deprecated: use defaultNetwork instead FloatingNetID - defines a openstack ID for the floatingNet.' + description: |- + Deprecated: use defaultNetwork instead + FloatingNetID defines a openstack ID for the floatingNet. type: string image: description: Image defines openstack image for the LoadBalancer. @@ -224,28 +230,28 @@ spec: virtual machines. type: string imageName: - description: NOT IMPLEMENTED ONLY ImageID is supported. - ImageName is the name of the image used for requesting - virtual machines. ImageName is only used if ImageID - is not defined. + description: |- + NOT IMPLEMENTED ONLY ImageID is supported. + ImageName is the name of the image used for requesting virtual machines. + ImageName is only used if ImageID is not defined. type: string imageSearch: - description: NOT IMPLEMENTED ONLY ImageID is supported. - ImageSearch is a search string to find the image - used for requesting virtual machines. Search will - be performed in metadata of the images. ImageSearch - is only used if ImageName and ImageID are not defined. + description: |- + NOT IMPLEMENTED ONLY ImageID is supported. + ImageSearch is a search string to find the image used for requesting virtual machines. + Search will be performed in metadata of the images. + ImageSearch is only used if ImageName and ImageID are not defined. type: string type: object networkID: - description: 'Deprecated: use defaultNetwork instead NetworkID - defines a openstack ID for the network.' + description: |- + Deprecated: use defaultNetwork instead + NetworkID defines a openstack ID for the network. type: string projectID: - description: ProjectID defines an openstack project ID - which will be used instead of the project from the secret - ref. If not set the project from the secret ref will - be used. + description: |- + ProjectID defines an openstack project ID which will be used instead of the project from the secret ref. + If not set the project from the secret ref will be used. type: string required: - authSecretRef @@ -299,42 +305,42 @@ spec: description: Conditions contains condition information for a LoadBalancerSet. items: description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" properties: lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. format: date-time type: string message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. maxLength: 32768 type: string observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. format: int64 minimum: 0 type: integer reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. This field may not be empty. maxLength: 1024 minLength: 1 @@ -348,11 +354,12 @@ spec: - Unknown type: string type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/charts/yawol-controller/templates/yawol-controller.yaml b/charts/yawol-controller/templates/yawol-controller.yaml index 2a8a7c3d..4dafd93f 100644 --- a/charts/yawol-controller/templates/yawol-controller.yaml +++ b/charts/yawol-controller/templates/yawol-controller.yaml @@ -122,6 +122,12 @@ spec: {{- if .Values.yawolController.errorBackoffMaxDelay }} - --error-backoff-max-delay={{ .Values.yawolController.errorBackoffMaxDelay }} {{- end }} + {{- range .Values.ntp.pools }} + - --ntp-pool={{ . }} + {{- end }} + {{- range .Values.ntp.servers }} + - --ntp-server={{ . }} + {{- end }} {{- include "logFlags" . | indent 10 }} env: {{- if .Values.namespace }} diff --git a/charts/yawol-controller/values.yaml b/charts/yawol-controller/values.yaml index 6b027d0d..b9b536a2 100644 --- a/charts/yawol-controller/values.yaml +++ b/charts/yawol-controller/values.yaml @@ -88,6 +88,14 @@ resources: #openstackTimeout: 20s #yawolletRequeueTime: 60 +# NTP pools/servers to configure on LoadBalancerMachines. +# If neither pools nor servers are set, it defaults to using pool.ntp.org. +ntp: + # list of NTP pools + pools: [] + # list of individual NTP servers + servers: [] + # the name of the Kubernetes secret that contains the .openrc file contents # with the correct permissions to connect to the OpenStack API # diff --git a/cmd/yawol-controller/main.go b/cmd/yawol-controller/main.go index d0e2a162..4c270843 100644 --- a/cmd/yawol-controller/main.go +++ b/cmd/yawol-controller/main.go @@ -67,6 +67,9 @@ func main() { var yawolletRequeueTime int var lbmDeletionGracePeriod time.Duration + var ntpPools []string + var ntpServers []string + var openstackTimeout time.Duration // settings for leases @@ -114,6 +117,13 @@ func main() { "Grace period before deleting a load balancer machine AFTER the machine has first been identified as unready.", ) + fs.StringSliceVar(&ntpPools, "ntp-pool", ntpPools, + "List of NTP pools to configure on LoadBalancerMachines. Can be specified multiple times. "+ + "If neither ntp-pool nor ntp-server is set, it defaults to using pool.ntp.org.") + fs.StringSliceVar(&ntpServers, "ntp-server", ntpServers, + "List of individual NTP servers to configure on LoadBalancerMachines. Can be specified multiple times. "+ + "If neither ntp-pool nor ntp-server is set, it defaults to using pool.ntp.org.") + fs.DurationVar(&openstackTimeout, "openstack-timeout", 20*time.Second, "Timeout for all requests against Openstack.") fs.IntVar(&leasesDurationInt, "leases-duration", 60, @@ -300,6 +310,8 @@ func main() { Metrics: &helpermetrics.LoadBalancerMachineMetrics, OpenstackTimeout: openstackTimeout, YawolletRequeueTime: yawolletRequeueTime, + NTPPools: ntpPools, + NTPServers: ntpServers, DiscoveryClient: discoveryClient, RateLimiter: rateLimiter, }).SetupWithManager(loadBalancerMachineMgr); err != nil { diff --git a/controllers/yawol-controller/loadbalancermachine/loadbalancermachine_controller.go b/controllers/yawol-controller/loadbalancermachine/loadbalancermachine_controller.go index 6fd35989..1c1cc8ca 100644 --- a/controllers/yawol-controller/loadbalancermachine/loadbalancermachine_controller.go +++ b/controllers/yawol-controller/loadbalancermachine/loadbalancermachine_controller.go @@ -63,6 +63,8 @@ type LoadBalancerMachineReconciler struct { //nolint:revive // naming from kubeb WorkerCount int OpenstackTimeout time.Duration YawolletRequeueTime int + NTPPools []string + NTPServers []string DiscoveryClient *discovery.DiscoveryClient RateLimiter ratelimiter.RateLimiter } @@ -429,7 +431,7 @@ func (r *LoadBalancerMachineReconciler) reconcileRoleBinding( return nil } -func (r *LoadBalancerMachineReconciler) reconcilePort( //nolint: gocyclo // TODO reduce complexity in future +func (r *LoadBalancerMachineReconciler) reconcilePort( // nolint: gocyclo // TODO reduce complexity in future ctx context.Context, osClient os.Client, req ctrl.Request, @@ -439,20 +441,20 @@ func (r *LoadBalancerMachineReconciler) reconcilePort( //nolint: gocyclo // TODO var err error // TODO cleanup after removing deprecated fields - if lbm.Status.PortID != nil && lbm.Status.DefaultPortID == nil { //nolint: staticcheck // needed to be backwards compatible + if lbm.Status.PortID != nil && lbm.Status.DefaultPortID == nil { // nolint: staticcheck // needed to be backwards compatible if err := helper.PatchLBMStatus( ctx, r.Client.Status(), lbm, - yawolv1beta1.LoadBalancerMachineStatus{DefaultPortID: lbm.Status.PortID}, //nolint: staticcheck // needed to be backwards compatible + yawolv1beta1.LoadBalancerMachineStatus{DefaultPortID: lbm.Status.PortID}, // nolint: staticcheck // needed to be backwards compatible ); err != nil { return err } } // TODO cleanup after removing deprecated fields - if lbm.Status.PortID != nil && lbm.Status.DefaultPortID != nil && //nolint: staticcheck // needed to be backwards compatible - *lbm.Status.PortID == *lbm.Status.DefaultPortID { //nolint: staticcheck // needed to be backwards compatible + if lbm.Status.PortID != nil && lbm.Status.DefaultPortID != nil && // nolint: staticcheck // needed to be backwards compatible + *lbm.Status.PortID == *lbm.Status.DefaultPortID { // nolint: staticcheck // needed to be backwards compatible if err := helper.RemoveFromLBMStatus(ctx, r.Client.Status(), lbm, "portID"); err != nil { return err } @@ -460,8 +462,8 @@ func (r *LoadBalancerMachineReconciler) reconcilePort( //nolint: gocyclo // TODO // TODO cleanup after removing deprecated fields var networkID string - if lbm.Spec.Infrastructure.NetworkID != "" { //nolint: staticcheck // needed to be backwards compatible - networkID = lbm.Spec.Infrastructure.NetworkID //nolint: staticcheck // needed to be backwards compatible + if lbm.Spec.Infrastructure.NetworkID != "" { // nolint: staticcheck // needed to be backwards compatible + networkID = lbm.Spec.Infrastructure.NetworkID // nolint: staticcheck // needed to be backwards compatible } if lbm.Spec.Infrastructure.DefaultNetwork.NetworkID != "" { networkID = lbm.Spec.Infrastructure.DefaultNetwork.NetworkID @@ -685,6 +687,8 @@ func (r *LoadBalancerMachineReconciler) reconcileServer( loadBalancerMachine, vip, r.YawolletRequeueTime, + r.NTPPools, + r.NTPServers, ) if err != nil { return err @@ -766,8 +770,8 @@ func (r *LoadBalancerMachineReconciler) createServer( // TODO cleanup after removing deprecated fields var networkID string - if loadBalancerMachine.Spec.Infrastructure.NetworkID != "" { //nolint: staticcheck // needed to be backwards compatible - networkID = loadBalancerMachine.Spec.Infrastructure.NetworkID //nolint: staticcheck // needed to be backwards compatible + if loadBalancerMachine.Spec.Infrastructure.NetworkID != "" { // nolint: staticcheck // needed to be backwards compatible + networkID = loadBalancerMachine.Spec.Infrastructure.NetworkID // nolint: staticcheck // needed to be backwards compatible } if loadBalancerMachine.Spec.Infrastructure.DefaultNetwork.NetworkID != "" { networkID = loadBalancerMachine.Spec.Infrastructure.DefaultNetwork.NetworkID @@ -922,8 +926,8 @@ func (r *LoadBalancerMachineReconciler) deletePort( } // TODO cleanup after removing deprecated fields - if lbm.Status.PortID != nil { //nolint: staticcheck // needed to be backwards compatible - //nolint: staticcheck // needed to be backwards compatible + if lbm.Status.PortID != nil { // nolint: staticcheck // needed to be backwards compatible + // nolint: staticcheck // needed to be backwards compatible if err = openstackhelper.DeletePort(ctx, portClient, *lbm.Status.PortID); err != nil { switch err.(type) { case gophercloud.ErrDefault404: @@ -977,7 +981,7 @@ func (r *LoadBalancerMachineReconciler) deleteSA( Namespace: lbm.Namespace, }, } - if err := r.Client.Delete(ctx, &sa); client.IgnoreNotFound(err) != nil { //nolint: gocritic // ignore of not found is intended + if err := r.Client.Delete(ctx, &sa); client.IgnoreNotFound(err) != nil { // nolint: gocritic // ignore of not found is intended return err } @@ -994,7 +998,7 @@ func (r *LoadBalancerMachineReconciler) deleteRoleBinding( Namespace: lbm.Namespace, }, } - if err := r.Client.Delete(ctx, &rb); client.IgnoreNotFound(err) != nil { //nolint: gocritic // ignore of not found is intended + if err := r.Client.Delete(ctx, &rb); client.IgnoreNotFound(err) != nil { // nolint: gocritic // ignore of not found is intended return err } @@ -1011,7 +1015,7 @@ func (r *LoadBalancerMachineReconciler) deleteRole( Namespace: lbm.Namespace, }, } - if err := r.Client.Delete(ctx, &role); client.IgnoreNotFound(err) != nil { //nolint: gocritic // ignore of not found is intended + if err := r.Client.Delete(ctx, &role); client.IgnoreNotFound(err) != nil { // nolint: gocritic // ignore of not found is intended return err } diff --git a/go.mod b/go.mod index d9052729..41ae8879 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,15 @@ require ( github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 github.com/envoyproxy/go-control-plane v0.13.0 github.com/go-logr/logr v1.4.2 + github.com/go-task/slim-sprig/v3 v3.0.0 github.com/golang/protobuf v1.5.4 github.com/gophercloud/gophercloud v1.13.0 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/onsi/ginkgo/v2 v2.20.2 github.com/onsi/gomega v1.34.2 github.com/prometheus/client_golang v1.20.1 + github.com/shirou/gopsutil/v4 v4.24.7 + github.com/spf13/pflag v1.0.5 go.uber.org/zap v1.27.0 golang.org/x/time v0.6.0 google.golang.org/grpc v1.65.0 @@ -29,8 +32,6 @@ require ( sigs.k8s.io/controller-tools v0.15.0 ) -require github.com/shirou/gopsutil/v4 v4.24.7 - require ( cel.dev/expr v0.15.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -48,7 +49,6 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobuffalo/flect v1.0.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -80,7 +80,6 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cobra v1.8.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect diff --git a/internal/helper/loadbalancermachine.go b/internal/helper/loadbalancermachine.go index fd9047f5..35b9bbde 100644 --- a/internal/helper/loadbalancermachine.go +++ b/internal/helper/loadbalancermachine.go @@ -3,22 +3,38 @@ package helper import ( "bytes" "context" + _ "embed" "encoding/base64" "encoding/json" "fmt" "strconv" + "text/template" "time" - yawolv1beta1 "github.com/stackitcloud/yawol/api/v1beta1" - helpermetrics "github.com/stackitcloud/yawol/internal/metrics" - + sprig "github.com/go-task/slim-sprig/v3" "gopkg.in/yaml.v3" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "sigs.k8s.io/controller-runtime/pkg/client" + + yawolv1beta1 "github.com/stackitcloud/yawol/api/v1beta1" + helpermetrics "github.com/stackitcloud/yawol/internal/metrics" ) +var ( + //go:embed templates/chrony.conf.tpl + chronyConfigTemplateRaw string + chronyConfigTemplate *template.Template +) + +func init() { + var err error + chronyConfigTemplate, err = template.New("chrony.conf").Funcs(sprig.HermeticTxtFuncMap()).Parse(chronyConfigTemplateRaw) + utilruntime.Must(err) +} + // LoadBalancerMachineOpenstackReconcileIsNeeded returns true if an openstack reconcile is needed. func LoadBalancerMachineOpenstackReconcileIsNeeded(lbm *yawolv1beta1.LoadBalancerMachine) bool { // LastOpenstackReconcile is nil, first run @@ -251,6 +267,7 @@ func GenerateUserData( loadbalancerMachine *yawolv1beta1.LoadBalancerMachine, vip string, yawolletRequeueTime int, + ntpPools, ntpServers []string, ) (string, error) { var err error const ( @@ -300,9 +317,24 @@ func GenerateUserData( yawolletArgs = yawolletArgs + "--requeue-time=" + strconv.Itoa(yawolletRequeueTime) + " " } + // cloud-init also features an NTP module that can configure chrony, see + // https://cloudinit.readthedocs.io/en/latest/reference/modules.html#ntp. + // We don't use cloud-init's NTP module as we want to set the `cmdport` directive. To do so via the cloud-config, + // we would need to write a jinja template for the chrony config file. I.e., we would have gain nothing over + // generating the chrony config ourselves and explicitly restarting the service. + chronyConfig, err := generateChronyConfig(ntpPools, ntpServers) + if err != nil { + return "", fmt.Errorf("failed generating chrony config: %w", err) + } + return ` #cloud-config write_files: +- encoding: b64 + content: ` + base64.StdEncoding.EncodeToString(chronyConfig) + ` + owner: root:root + path: /etc/chrony/chrony.conf + permissions: '0644' - encoding: b64 content: ` + kubeconfigBase64 + ` owner: yawol:yawol @@ -327,6 +359,7 @@ write_files: YAWOLLET_ARGS="` + yawolletArgs + `" path: /etc/yawol/env.conf runcmd: + - [ /sbin/rc-service, chronyd, restart ] - [ /sbin/rc-service, promtail, ` + promtailOpenRCState + ` ] - [ /sbin/rc-update, ` + promtailOpenRC + `, promtail, default ] - [ /sbin/rc-service, sshd, ` + sshOpenRCState + ` ] @@ -337,6 +370,20 @@ runcmd: `, nil } +func generateChronyConfig(pools, servers []string) ([]byte, error) { + // If neither --ntp-pool nor --ntp-server is set, default to using pool.ntp.org (resembles the default chrony config). + if len(pools) == 0 && len(servers) == 0 { + pools = []string{"pool.ntp.org"} + } + + conf := &bytes.Buffer{} + err := chronyConfigTemplate.Execute(conf, map[string]any{ + "pools": pools, + "servers": servers, + }) + return conf.Bytes(), err +} + func generateKeepalivedConfig(vip string) string { return `! Configuration File for keepalived diff --git a/internal/helper/loadbalancermachine_test.go b/internal/helper/loadbalancermachine_test.go index caa2a688..56760bd5 100644 --- a/internal/helper/loadbalancermachine_test.go +++ b/internal/helper/loadbalancermachine_test.go @@ -160,3 +160,117 @@ var _ = DescribeTable("areRelevantConditionsMet", expect: false, }), ) + +var _ = Describe("generateChronyConfig", func() { + var ( + pools, servers []string + ) + + BeforeEach(func() { + pools = []string{"pool.a.org", "pool.b.org"} + servers = []string{"ntp.a.org", "10.0.10.1"} + }) + + It("should use pool.ntp.org if neither pools nor servers are specified", func() { + Expect(generateChronyConfig(nil, nil)).To(BeEquivalentTo(`pool pool.ntp.org iburst +# Settings from alpine default chrony config +driftfile /var/lib/chrony/chrony.drift +rtcsync +# prevent chrony from opening ports on the LoadBalancer machine +cmdport 0 + +# Settings from cloud-init generated chrony config +# Stop bad estimates upsetting machine clock. +maxupdateskew 100.0 +# Step the system clock instead of slewing it if the adjustment is larger than +# one second, but only in the first three clock updates. +makestep 1 3 +`)) + }) + + It("should use the configured pools", func() { + Expect(generateChronyConfig(pools[:1], nil)).To(BeEquivalentTo(`pool pool.a.org iburst +# Settings from alpine default chrony config +driftfile /var/lib/chrony/chrony.drift +rtcsync +# prevent chrony from opening ports on the LoadBalancer machine +cmdport 0 + +# Settings from cloud-init generated chrony config +# Stop bad estimates upsetting machine clock. +maxupdateskew 100.0 +# Step the system clock instead of slewing it if the adjustment is larger than +# one second, but only in the first three clock updates. +makestep 1 3 +`)) + + Expect(generateChronyConfig(pools, nil)).To(BeEquivalentTo(`pool pool.a.org iburst +pool pool.b.org iburst +# Settings from alpine default chrony config +driftfile /var/lib/chrony/chrony.drift +rtcsync +# prevent chrony from opening ports on the LoadBalancer machine +cmdport 0 + +# Settings from cloud-init generated chrony config +# Stop bad estimates upsetting machine clock. +maxupdateskew 100.0 +# Step the system clock instead of slewing it if the adjustment is larger than +# one second, but only in the first three clock updates. +makestep 1 3 +`)) + }) + + It("should use the configured servers", func() { + Expect(generateChronyConfig(nil, servers[:1])).To(BeEquivalentTo(`server ntp.a.org iburst +# Settings from alpine default chrony config +driftfile /var/lib/chrony/chrony.drift +rtcsync +# prevent chrony from opening ports on the LoadBalancer machine +cmdport 0 + +# Settings from cloud-init generated chrony config +# Stop bad estimates upsetting machine clock. +maxupdateskew 100.0 +# Step the system clock instead of slewing it if the adjustment is larger than +# one second, but only in the first three clock updates. +makestep 1 3 +`)) + + Expect(generateChronyConfig(nil, servers)).To(BeEquivalentTo(`server ntp.a.org iburst +server 10.0.10.1 iburst +# Settings from alpine default chrony config +driftfile /var/lib/chrony/chrony.drift +rtcsync +# prevent chrony from opening ports on the LoadBalancer machine +cmdport 0 + +# Settings from cloud-init generated chrony config +# Stop bad estimates upsetting machine clock. +maxupdateskew 100.0 +# Step the system clock instead of slewing it if the adjustment is larger than +# one second, but only in the first three clock updates. +makestep 1 3 +`)) + }) + + It("should use both the configured pools and servers", func() { + Expect(generateChronyConfig(pools, servers)).To(BeEquivalentTo(`pool pool.a.org iburst +pool pool.b.org iburst +server ntp.a.org iburst +server 10.0.10.1 iburst +# Settings from alpine default chrony config +driftfile /var/lib/chrony/chrony.drift +rtcsync +# prevent chrony from opening ports on the LoadBalancer machine +cmdport 0 + +# Settings from cloud-init generated chrony config +# Stop bad estimates upsetting machine clock. +maxupdateskew 100.0 +# Step the system clock instead of slewing it if the adjustment is larger than +# one second, but only in the first three clock updates. +makestep 1 3 +`)) + }) +}) diff --git a/internal/helper/templates/chrony.conf.tpl b/internal/helper/templates/chrony.conf.tpl new file mode 100644 index 00000000..ab26fad8 --- /dev/null +++ b/internal/helper/templates/chrony.conf.tpl @@ -0,0 +1,19 @@ +{{- range .pools -}} +pool {{ . }} iburst +{{ end -}} +{{- range .servers -}} +server {{ . }} iburst +{{ end -}} + +# Settings from alpine default chrony config +driftfile /var/lib/chrony/chrony.drift +rtcsync +# prevent chrony from opening ports on the LoadBalancer machine +cmdport 0 + +# Settings from cloud-init generated chrony config +# Stop bad estimates upsetting machine clock. +maxupdateskew 100.0 +# Step the system clock instead of slewing it if the adjustment is larger than +# one second, but only in the first three clock updates. +makestep 1 3