├── data ├── common.yaml ├── os │ ├── Debian.yaml │ └── RedHat.yaml └── rspec.yaml ├── templates ├── node │ └── kubelet.config.epp ├── server │ ├── etcd │ │ ├── cluster.conf.epp │ │ └── etcd.conf.epp │ ├── pql_query.epp │ ├── resources │ │ └── coredns_corefile.epp │ └── tls │ │ └── openssl.cnf.epp ├── sysconfig.epp ├── etcd.service.epp └── service.epp ├── .gitattributes ├── types ├── uri.pp ├── ensure.pp ├── portrange.pp ├── version.pp ├── firewall.pp ├── node_auth.pp ├── proxy_auth.pp ├── bootstrap_token.pp ├── container_runtimes.pp ├── extended_key_usage.pp ├── proxy_method.pp ├── duration.pp ├── tls_altnames.pp ├── quantity.pp ├── ip_addresses.pp ├── native_packaging.pp ├── timestamp.pp ├── cidr.pp └── node_role.pp ├── spec ├── spec_helper_local.rb ├── support │ ├── spec │ │ └── tmpfilename.rb │ └── shared_behaviour.rb ├── default_module_facts.yml ├── classes │ ├── install │ │ ├── crictl_spec.rb │ │ ├── kubeadm_spec.rb │ │ ├── kubectl_spec.rb │ │ ├── cni_plugins_spec.rb │ │ └── container_runtime_spec.rb │ ├── server_spec.rb │ ├── node_spec.rb │ ├── server │ │ ├── resources_spec.rb │ │ ├── wait_online_spec.rb │ │ ├── resources │ │ │ ├── coredns_spec.rb │ │ │ ├── kube_proxy_spec.rb │ │ │ ├── boostrap_spec.rb │ │ │ └── flannel_spec.rb │ │ ├── tls_spec.rb │ │ ├── scheduler_spec.rb │ │ ├── etcd │ │ │ └── setup_spec.rb │ │ ├── etcd_spec.rb │ │ └── controller_manager_spec.rb │ ├── node │ │ ├── kubelet_spec.rb │ │ └── kube_proxy_spec.rb │ ├── k8s_spec.rb │ └── repo_spec.rb ├── functions │ └── k8s │ │ ├── format_url_spec.rb │ │ ├── format_arguments_spec.rb │ │ └── ip_in_cidr.rb ├── type_aliases │ ├── portrange_spec.rb │ ├── ensure_spec.rb │ ├── firewall.rb │ ├── auth_spec.rb │ ├── node_auth_spec.rb │ ├── proxy_auth_spec.rb │ ├── version_spec.rb │ ├── container_runtimes.rb │ ├── quantity_spec.rb │ ├── extended_key_usage_spec.rb │ ├── proxy_method_spec.rb │ ├── uri_spec.rb │ ├── node_role_spec.rb │ ├── duration_spec.rb │ ├── native_packaging_spec.rb │ ├── bootstrap_token_spec.rb │ ├── tls_altnames.rb │ ├── cidr_spec.rb │ ├── ip_addresses_spec.rb │ └── timestamp_spec.rb ├── spec_helper.rb ├── defines │ ├── server │ │ ├── etcd │ │ │ └── member_spec.rb │ │ ├── bootstrap_token_spec.rb │ │ └── tls │ │ │ ├── ca_spec.rb │ │ │ └── cert_spec.rb │ └── binary_spec.rb ├── fixtures │ └── files │ │ └── resources │ │ └── kube-proxy.yaml └── unit │ └── puppet │ ├── provider │ └── kubeconfig │ │ ├── ruby_spec.rb │ │ └── kubectl_spec.rb │ └── type │ ├── kubectl_apply_spec.rb │ └── kubeconfig_spec.rb ├── .msync.yml ├── .github ├── labeler.yml ├── workflows │ ├── labeler.yml │ ├── ci.yml │ ├── release.yml │ └── prepare_release.yml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── release.yml ├── .puppet-lint.rc ├── .rubocop.yml ├── .sync.yml ├── manifests ├── node │ ├── kubectl.pp │ ├── simple_cni.pp │ └── kube_proxy.pp ├── install │ ├── kubeadm.pp │ ├── kubectl.pp │ ├── crictl.pp │ ├── container_runtime.pp │ └── cni_plugins.pp ├── server │ ├── tls │ │ ├── k8s_sign.pp │ │ ├── ca.pp │ │ └── cert.pp │ ├── wait_online.pp │ ├── etcd │ │ └── member.pp │ ├── bootstrap_token.pp │ ├── scheduler.pp │ └── controller_manager.pp ├── common.pp ├── node.pp ├── repo.pp └── binary.pp ├── examples ├── simple_setup │ ├── data │ │ ├── nodes │ │ │ ├── worker.yaml │ │ │ └── controller.yaml │ │ └── common.yaml │ ├── Readme.md │ └── manifests │ │ ├── worker.pp │ │ └── controller.pp └── cilium │ └── Readme.md ├── lib ├── facter │ └── k8s_ca.rb └── puppet │ ├── functions │ └── k8s │ │ ├── format_arguments.rb │ │ ├── ip_in_cidr.rb │ │ └── format_url.rb │ ├── provider │ ├── kubectl_apply │ │ ├── file.rb │ │ └── kubectl.rb │ └── kubeconfig │ │ └── kubectl.rb │ ├── util │ └── k8s.rb │ └── type │ ├── kubectl_apply.rb │ └── kubeconfig.rb ├── hiera-rspec.yaml ├── .editorconfig ├── .fixtures.yml ├── .gitignore ├── files └── containerd │ └── config.toml ├── .pmtignore ├── hiera.yaml ├── Gemfile ├── Rakefile ├── .overcommit.yml ├── .rubocop_todo.yml ├── metadata.json └── README.md /data/common.yaml: -------------------------------------------------------------------------------- 1 | --- {} 2 | -------------------------------------------------------------------------------- /templates/node/kubelet.config.epp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/os/Debian.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | k8s::sysconfig_path: /etc/default 3 | -------------------------------------------------------------------------------- /data/os/RedHat.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | k8s::sysconfig_path: /etc/sysconfig 3 | -------------------------------------------------------------------------------- /data/rspec.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | k8s::server::etcd_servers: ['https://localhost:2379'] 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.rb eol=lf 2 | *.erb eol=lf 3 | *.pp eol=lf 4 | *.sh eol=lf 5 | *.epp eol=lf 6 | -------------------------------------------------------------------------------- /types/uri.pp: -------------------------------------------------------------------------------- 1 | # @summary This regexp matches URI values 2 | type K8s::URI = Pattern[/^[a-z]+:\/\//] 3 | -------------------------------------------------------------------------------- /types/ensure.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe the ensure pattern 2 | type K8s::Ensure = Enum['present', 'absent'] 3 | -------------------------------------------------------------------------------- /types/portrange.pp: -------------------------------------------------------------------------------- 1 | # @summary This regexp matches port range values 2 | type K8s::PortRange = Pattern[/^[0-9]+(-[0-9]+)?$/] 3 | -------------------------------------------------------------------------------- /types/version.pp: -------------------------------------------------------------------------------- 1 | # @summary A type for handling Kubernetes version numbers 2 | type K8s::Version = Pattern[/^(\d+\.){2}\d+$/] 3 | -------------------------------------------------------------------------------- /spec/spec_helper_local.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Require all support files 4 | Dir['./spec/support/**/*.rb'].sort.each { |f| require f } 5 | -------------------------------------------------------------------------------- /types/firewall.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe the type of the firewall to use 2 | type K8s::Firewall = Enum[ 3 | 'iptables', 4 | 'firewalld', 5 | ] 6 | -------------------------------------------------------------------------------- /types/node_auth.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe node/kubelet authentication methods 2 | type K8s::Node_auth = Enum[ 3 | 'cert', 4 | 'token', 5 | 'bootstrap' 6 | ] 7 | -------------------------------------------------------------------------------- /types/proxy_auth.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe kube-proxy authentication methods 2 | type K8s::Proxy_auth = Enum[ 3 | 'cert', 4 | 'token', 5 | 'incluster' 6 | ] 7 | -------------------------------------------------------------------------------- /.msync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | modulesync_config_version: '10.4.0' 6 | -------------------------------------------------------------------------------- /types/bootstrap_token.pp: -------------------------------------------------------------------------------- 1 | # @summary A Kubernetes bootstrap token, must be 16-characters lowercase alphanumerical 2 | type K8s::Bootstrap_token = Pattern[/\A[a-z0-9]{16}\z/] 3 | -------------------------------------------------------------------------------- /types/container_runtimes.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe the supported container runtimes 2 | type K8s::Container_runtimes = Enum[ 3 | 'crio', 4 | 'containerd' 5 | ] 6 | -------------------------------------------------------------------------------- /templates/server/etcd/cluster.conf.epp: -------------------------------------------------------------------------------- 1 | <% | 2 | Array[String] $initial_cluster, 3 | | -%> 4 | # Managed by Puppet 5 | 6 | ETCD_INITIAL_CLUSTER=<%= join($initial_cluster, ',') %> 7 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | skip-changelog: 6 | - head-branch: ['^release-*', 'release'] 7 | -------------------------------------------------------------------------------- /types/extended_key_usage.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe extended key usage for a TLS certificate 2 | type K8s::Extended_key_usage = Array[ 3 | Enum[ 4 | 'clientAuth', 5 | 'serverAuth' 6 | ] 7 | ] 8 | -------------------------------------------------------------------------------- /types/proxy_method.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe how kube-proxy should be deployed 2 | type K8s::Proxy_method = Variant[ 3 | Enum[ 4 | 'on-node', 5 | 'in-cluster', 6 | ], 7 | Boolean 8 | ] 9 | -------------------------------------------------------------------------------- /types/duration.pp: -------------------------------------------------------------------------------- 1 | # @summary This regexp matches Go duration values, as taken from; 2 | # https://golang.org/pkg/time/#ParseDuration 3 | type K8s::Duration = Pattern[/^(-?[0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$/] 4 | -------------------------------------------------------------------------------- /types/tls_altnames.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe TLS alternative names in certificates 2 | type K8s::TLS_altnames = Array[ 3 | Variant[ 4 | Stdlib::Fqdn, 5 | Stdlib::IP::Address::Nosubnet, 6 | ] 7 | ] 8 | -------------------------------------------------------------------------------- /.puppet-lint.rc: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | --fail-on-warnings 5 | --no-parameter_documentation-check 6 | --no-parameter_types-check 7 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | inherit_from: .rubocop_todo.yml 6 | inherit_gem: 7 | voxpupuli-test: rubocop.yml 8 | -------------------------------------------------------------------------------- /spec/support/spec/tmpfilename.rb: -------------------------------------------------------------------------------- 1 | def tmpfilename(name) 2 | source = Tempfile.new(name) 3 | path = source.path 4 | source.close! 5 | $global_tempfiles ||= [] 6 | $global_tempfiles << File.expand_path(path) 7 | path 8 | end 9 | -------------------------------------------------------------------------------- /types/quantity.pp: -------------------------------------------------------------------------------- 1 | # @summary This regexp matches quantities, like those for resource requests/limits 2 | type K8s::Quantity = Pattern[/^[+-]?([0-9]+|[0-9]+\.[0-9]{1,3}|\.[0-9]{1,3}|[0-9]+\.)([KMGTPE]i|[mkMGTPE]|[eE][0-9]+(\.[0-9]+)?)?$/] 3 | -------------------------------------------------------------------------------- /.sync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spec/spec_helper.rb: 3 | spec_overrides: 4 | - "Dir['./spec/support/**/*.rb'].sort.each { |f| require f }" 5 | facterdb_string_keys: true 6 | 7 | .github/workflows/ci.yml: 8 | with: 9 | rubocop: false 10 | -------------------------------------------------------------------------------- /types/ip_addresses.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe multiple IP addresses without subnet sizes 2 | type K8s::IP_addresses = Variant[ 3 | Stdlib::IP::Address::Nosubnet, 4 | Array[ 5 | Stdlib::IP::Address::Nosubnet, 6 | 1 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /manifests/node/kubectl.pp: -------------------------------------------------------------------------------- 1 | # @summary Installs the kubectl binary 2 | # 3 | # @param ensure Whether to install the binary 4 | class k8s::node::kubectl ( 5 | K8s::Ensure $ensure = $k8s::ensure, 6 | ) { 7 | k8s::binary { 'kubectl': 8 | ensure => $ensure, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/simple_setup/data/nodes/worker.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | classes: 3 | - profile::k8s::worker 4 | 5 | ### K8S::Node 6 | # see common.yaml for the origin of the data 7 | k8s::node::node_token: "puppet.%{lookup('k8s::server::resources::bootstrap::secret')}" 8 | k8s::node::manage_crictl: true 9 | -------------------------------------------------------------------------------- /types/native_packaging.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe Kubernetes native packaging methods 2 | # 3 | # @note hyperkube is a legacy name for single-binary packages 4 | type K8s::Native_packaging = Enum[ 5 | 'package', 6 | 'tarball', 7 | 'loose', 8 | 'hyperkube', 9 | 'manual' 10 | ] 11 | -------------------------------------------------------------------------------- /manifests/install/kubeadm.pp: -------------------------------------------------------------------------------- 1 | # @summary Installs the kubeadm binary 2 | # 3 | # @param ensure set ensure for installation or deinstallation 4 | # 5 | class k8s::install::kubeadm ( 6 | K8s::Ensure $ensure = $k8s::ensure, 7 | ) { 8 | k8s::binary { 'kubeadm': 9 | ensure => $ensure, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /manifests/install/kubectl.pp: -------------------------------------------------------------------------------- 1 | # @summary Installs the kubectl binary 2 | # 3 | # @param ensure set ensure for installation or deinstallation 4 | # 5 | class k8s::install::kubectl ( 6 | K8s::Ensure $ensure = $k8s::ensure, 7 | ) { 8 | k8s::binary { 'kubectl': 9 | ensure => $ensure, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/facter/k8s_ca.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Adds a fact to read the locally stored Kubernetes CA 4 | Facter.add(:k8s_ca) do 5 | confine { File.exist? '/etc/kubernetes/certs/ca.pem' } 6 | setcode do 7 | Base64.strict_encode64(File.read('/etc/kubernetes/certs/ca.pem')) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /templates/server/pql_query.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | String $comment, 3 | Hash[String,String] $environment_variables, 4 | | -%> 5 | resources[certname] { 6 | type = 'Class' and 7 | title = 'K8s::Node::Kubelet' and 8 | parameters.puppetdb_discovery_tag = '${puppetdb_discovery_tag}' 9 | order by certname 10 | } 11 | -------------------------------------------------------------------------------- /hiera-rspec.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 5 3 | 4 | defaults: # Used for any hierarchy level that omits these keys. 5 | datadir: data # This path is relative to hiera.yaml's directory. 6 | data_hash: yaml_data # Use the built-in YAML backend. 7 | 8 | hierarchy: 9 | - name: 'rspec' 10 | path: 'rspec.yaml' 11 | -------------------------------------------------------------------------------- /types/timestamp.pp: -------------------------------------------------------------------------------- 1 | # @summary This regexp matches RFC3339 timestamps, the same as what Kubernetes expects to find 2 | type K8s::Timestamp = Pattern[/^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?([Zz]|[+-]([01][0-9]|2[0-3]):[0-5][0-9])$/] # lint:ignore:140chars 3 | -------------------------------------------------------------------------------- /types/cidr.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe one or more IPv4/6 CIDR 2 | type K8s::CIDR = Variant[ 3 | Stdlib::IP::Address::V4::CIDR, 4 | Stdlib::IP::Address::V6::CIDR, 5 | Array[ 6 | Variant[ 7 | Stdlib::IP::Address::V4::CIDR, 8 | Stdlib::IP::Address::V6::CIDR 9 | ], 10 | 1 11 | ] 12 | ] 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # Managed by modulesync - DO NOT EDIT 4 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 5 | 6 | root = true 7 | 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_size = 2 12 | tab_width = 2 13 | indent_style = space 14 | insert_final_newline = true 15 | trim_trailing_whitespace = true 16 | -------------------------------------------------------------------------------- /spec/default_module_facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ipaddress: "172.16.254.254" 3 | ipaddress6: "fe80:0000:0000:0000:aaaa:aaaa:aaaa:aaaa" 4 | is_pe: false 5 | macaddress: "aa:aa:aa:aa:aa:aa" 6 | networking: 7 | fqdn: 'node.example.com' 8 | hostname: 'node' 9 | ip: '172.16.254.254' 10 | ip6: "fe80:0000:0000:0000:aaaa:aaaa:aaaa:aaaa" 11 | os: 12 | architecture: x86_64 13 | -------------------------------------------------------------------------------- /templates/sysconfig.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | String $comment, 3 | Hash[String,String] $environment_variables, 4 | | -%> 5 | ### NB: File managed by Puppet. 6 | ### Any changes will be overwritten. 7 | # 8 | ## <%= $comment %> 9 | # 10 | 11 | <%- 12 | $environment_variables.each |$key, $value| { 13 | -%> 14 | <%= $key %>="<%= regsubst($value, '"', '\"', 'G') %>" 15 | <%- 16 | } 17 | -%> 18 | -------------------------------------------------------------------------------- /types/node_role.pp: -------------------------------------------------------------------------------- 1 | # @summary a type to describe a type of Kubernetes node 2 | # 3 | # @note server/control-plane are identical, one using the Puppet term, the other the Kubernetes term 4 | # @note none will install basic components, but not activate any services 5 | type K8s::Node_role = Enum[ 6 | 'node', 7 | 'server', 8 | 'control-plane', 9 | 'etcd-replica', 10 | 'none' 11 | ] 12 | -------------------------------------------------------------------------------- /spec/classes/install/crictl_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::install::crictl' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | include k8s 9 | PUPPET 10 | end 11 | 12 | on_supported_os.each do |os, os_facts| 13 | context "on #{os}" do 14 | let(:facts) { os_facts } 15 | 16 | it { is_expected.to compile } 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/classes/install/kubeadm_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::install::kubeadm' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | include k8s 9 | PUPPET 10 | end 11 | 12 | on_supported_os.each do |os, os_facts| 13 | context "on #{os}" do 14 | let(:facts) { os_facts } 15 | 16 | it { is_expected.to compile } 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/classes/install/kubectl_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::install::kubectl' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | include k8s 9 | PUPPET 10 | end 11 | 12 | on_supported_os.each do |os, os_facts| 13 | context "on #{os}" do 14 | let(:facts) { os_facts } 15 | 16 | it { is_expected.to compile } 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /.fixtures.yml: -------------------------------------------------------------------------------- 1 | --- 2 | fixtures: 3 | repositories: 4 | archive: https://github.com/voxpupuli/puppet-archive 5 | augeasproviders_core: https://github.com/voxpupuli/puppet-augeasproviders_core 6 | augeasproviders_sysctl: https://github.com/voxpupuli/puppet-augeasproviders_sysctl 7 | kmod: https://github.com/voxpupuli/puppet-kmod 8 | stdlib: https://github.com/puppetlabs/puppetlabs-stdlib 9 | systemd: https://github.com/voxpupuli/puppet-systemd 10 | -------------------------------------------------------------------------------- /examples/simple_setup/data/common.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | k8s::node::manage_simple_cni: true 3 | 4 | ### k8s::install::crictl 5 | k8s::install::crictl::config: 6 | 'runtime-endpoint': 'unix:///run/containerd/containerd.sock' 7 | 'image-endpoint': 'unix:///run/containerd/containerd.sock' 8 | 9 | # set token in an upper level, so that it can be used by controller and worker 10 | # see also worker.yaml for usage 11 | k8s::server::resources::bootstrap::secret: '0123456789abcdef' 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | /pkg/ 5 | /Gemfile.lock 6 | /Gemfile.local 7 | /vendor/ 8 | /.vendor/ 9 | /spec/fixtures/manifests/ 10 | /spec/fixtures/modules/ 11 | /.vagrant/ 12 | /.bundle/ 13 | /.ruby-version 14 | /coverage/ 15 | /log/ 16 | /.idea/ 17 | /.dependencies/ 18 | /.librarian/ 19 | /Puppetfile.lock 20 | *.iml 21 | .*.sw? 22 | /.yardoc/ 23 | /Guardfile 24 | bolt-debug.log 25 | .rerun.json 26 | -------------------------------------------------------------------------------- /files/containerd/config.toml: -------------------------------------------------------------------------------- 1 | ### Managed by Puppet 2 | 3 | version = 2 4 | 5 | [plugins] 6 | [plugins."io.containerd.grpc.v1.cri"] 7 | [plugins."io.containerd.grpc.v1.cri".containerd] 8 | [plugins."io.containerd.grpc.v1.cri".containerd.runtimes] 9 | [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] 10 | runtime_type = "io.containerd.runc.v2" 11 | 12 | [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] 13 | SystemdCgroup = true 14 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: "Pull Request Labeler" 6 | 7 | # yamllint disable-line rule:truthy 8 | on: 9 | pull_request_target: {} 10 | 11 | permissions: 12 | contents: read 13 | pull-requests: write 14 | 15 | jobs: 16 | labeler: 17 | permissions: 18 | contents: read 19 | pull-requests: write 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/labeler@v5 23 | -------------------------------------------------------------------------------- /spec/classes/server_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | include k8s 9 | PUPPET 10 | end 11 | 12 | let(:params) do 13 | { 14 | node_on_server: false, 15 | etcd_servers: ['https://localhost:2379'] 16 | } 17 | end 18 | 19 | on_supported_os.each do |os, os_facts| 20 | context "on #{os}" do 21 | let(:facts) { os_facts } 22 | 23 | it { is_expected.to compile } 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /templates/server/resources/coredns_corefile.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | Stdlib::Fqdn $cluster_domain, 3 | | -%> 4 | .:53 { 5 | errors 6 | health { 7 | lameduck 5s 8 | } 9 | ready 10 | kubernetes <%= $cluster_domain %> in-addr.arpa ip6.arpa { 11 | fallthrough in-addr.arpa ip6.arpa 12 | } 13 | prometheus :9153 14 | hosts /etc/coredns/PuppetHosts { 15 | ttl 60 16 | reload 15s 17 | fallthrough 18 | } 19 | forward . /etc/resolv.conf { 20 | max_concurrent 1000 21 | } 22 | cache 30 23 | loop 24 | reload 25 | loadbalance 26 | } 27 | -------------------------------------------------------------------------------- /spec/functions/k8s/format_url_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::format_url' do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}" do 8 | let(:facts) { os_facts } 9 | 10 | it { is_expected.not_to eq(nil) } 11 | it { is_expected.to run.with_params.and_raise_error(ArgumentError) } 12 | it { is_expected.to run.with_params('one').and_raise_error(ArgumentError) } 13 | it { is_expected.to run.with_params('format %{one}', { 'one' => 1 }).and_return('format 1') } 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: CI 6 | 7 | # yamllint disable-line rule:truthy 8 | on: 9 | pull_request: {} 10 | push: 11 | branches: 12 | - main 13 | - master 14 | 15 | concurrency: 16 | group: ${{ github.ref_name }} 17 | cancel-in-progress: true 18 | 19 | permissions: 20 | contents: read 21 | 22 | jobs: 23 | puppet: 24 | name: Puppet 25 | uses: voxpupuli/gha-puppet/.github/workflows/basic.yml@v4 26 | with: 27 | rubocop: false 28 | -------------------------------------------------------------------------------- /spec/classes/node_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::node' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | include ::k8s 9 | PUPPET 10 | end 11 | 12 | on_supported_os.each do |os, os_facts| 13 | context "on #{os}" do 14 | let(:facts) { os_facts } 15 | 16 | it { is_expected.to compile } 17 | 18 | if os_facts.dig('os', 'family') == 'Debian' 19 | it { is_expected.to contain_package 'conntrack' } 20 | else 21 | it { is_expected.to contain_package 'conntrack-tools' } 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | #### Pull Request (PR) description 10 | 13 | 14 | #### This Pull Request (PR) fixes the following issues 15 | 21 | -------------------------------------------------------------------------------- /examples/simple_setup/data/nodes/controller.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | classes: 3 | - profile::k8s::controller 4 | 5 | ### K8S::Server 6 | k8s::server::node_on_server: false 7 | 8 | k8s::server::etcd::generate_ca: true 9 | k8s::server::generate_ca: true 10 | k8s::server::manage_kubeadm: true 11 | 12 | ### K8S::Server::Apiserver 13 | # Choose an interface which is for cluster communications. 14 | # The apiserver will expose a port on the controller 15 | # and all the workers need to be able to reach it. 16 | k8s::server::apiserver::advertise_address: "%{facts.networking.interfaces.enp0s8.ip}" 17 | 18 | ### K8S::Server::Resources 19 | k8s::server::resources::manage_flannel: false 20 | -------------------------------------------------------------------------------- /.pmtignore: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | /docs/ 5 | /pkg/ 6 | /Gemfile 7 | /Gemfile.lock 8 | /Gemfile.local 9 | /vendor/ 10 | /.vendor/ 11 | /spec/ 12 | /Rakefile 13 | /.vagrant/ 14 | /.bundle/ 15 | /.ruby-version 16 | /coverage/ 17 | /log/ 18 | /.idea/ 19 | /.dependencies/ 20 | /.github/ 21 | /.librarian/ 22 | /Puppetfile.lock 23 | /Puppetfile 24 | *.iml 25 | /.editorconfig 26 | /.fixtures.yml 27 | /.gitignore 28 | /.msync.yml 29 | /.overcommit.yml 30 | /.pmtignore 31 | /.rspec 32 | /.rspec_parallel 33 | /.rubocop.yml 34 | /.sync.yml 35 | .*.sw? 36 | /.yardoc/ 37 | /.yardopts 38 | /Dockerfile 39 | /HISTORY.md 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: Release 6 | 7 | # yamllint disable-line rule:truthy 8 | on: 9 | push: 10 | tags: 11 | - '*' 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | release: 18 | name: Release 19 | uses: voxpupuli/gha-puppet/.github/workflows/release.yml@v3 20 | with: 21 | allowed_owner: 'voxpupuli' 22 | secrets: 23 | # Configure secrets here: 24 | # https://docs.github.com/en/actions/security-guides/encrypted-secrets 25 | username: ${{ secrets.PUPPET_FORGE_USERNAME }} 26 | api_key: ${{ secrets.PUPPET_FORGE_API_KEY }} 27 | -------------------------------------------------------------------------------- /spec/type_aliases/portrange_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::PortRange' do 6 | describe 'valid port range' do 7 | %w[ 8 | 80 9 | 443 10 | 9090-9099 11 | 1-5 12 | ].each do |value| 13 | describe value.inspect do 14 | it { is_expected.to allow_value(value) } 15 | end 16 | end 17 | end 18 | 19 | describe 'invalid port range' do 20 | [ 21 | nil, 22 | [nil], 23 | [nil, nil], 24 | { 'foo' => 'bar' }, 25 | {}, 26 | '', 27 | '-5', 28 | '0.2', 29 | ].each do |value| 30 | describe value.inspect do 31 | it { is_expected.not_to allow_value(value) } 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /templates/etcd.service.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | Stdlib::Unixpath $binary_path, 3 | Stdlib::Unixpath $workdir_path, 4 | String[1] $user, 5 | | -%> 6 | # THIS FILE IS MANAGED BY PUPPET 7 | 8 | [Unit] 9 | Description=etcd key-value store 10 | Documentation=https://github.com/etcd-io/etcd 11 | After=network-online.target local-fs.target remote-fs.target time-sync.target 12 | Wants=network-online.target local-fs.target remote-fs.target time-sync.target 13 | 14 | [Service] 15 | User=<%= $user %> 16 | Type=notify 17 | EnvironmentFile=/etc/etcd/etcd.conf 18 | EnvironmentFile=/etc/etcd/cluster.conf 19 | ExecStart=<%= $binary_path %> 20 | Restart=always 21 | RestartSec=10s 22 | LimitNOFILE=40000 23 | WorkingDirectory=<%= $workdir_path %> 24 | 25 | [Install] 26 | WantedBy=multi-user.target 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ## Affected Puppet, Ruby, OS and module versions/distributions 12 | 13 | - Puppet: 14 | - Ruby: 15 | - Distribution: 16 | - Module version: 17 | 18 | ## How to reproduce (e.g Puppet code you use) 19 | 20 | ## What are you seeing 21 | 22 | ## What behaviour did you expect instead 23 | 24 | ## Output log 25 | 26 | ## Any additional information you'd like to impart 27 | -------------------------------------------------------------------------------- /hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 5 3 | 4 | defaults: # Used for any hierarchy level that omits these keys. 5 | datadir: data # This path is relative to hiera.yaml's directory. 6 | data_hash: yaml_data # Use the built-in YAML backend. 7 | 8 | hierarchy: 9 | - name: "osfamily/major release" 10 | paths: 11 | - "os/%{facts.os.family}/%{facts.os.release.major}.yaml" 12 | # Used for Solaris 13 | - "os/%{facts.os.family}/%{facts.kernelrelease}.yaml" 14 | # Used to distinguish between Debian and Ubuntu 15 | - "os/%{facts.os.name}/%{facts.os.release.major}.yaml" 16 | - name: "osfamily" 17 | paths: 18 | - "os/%{facts.os.family}.yaml" 19 | - "os/%{facts.os.name}.yaml" 20 | - name: 'common' 21 | path: 'common.yaml' 22 | -------------------------------------------------------------------------------- /spec/type_aliases/ensure_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Ensure' do 6 | describe 'valid ensure' do 7 | %w[ 8 | present 9 | absent 10 | ].each do |value| 11 | describe value.inspect do 12 | it { is_expected.to allow_value(value) } 13 | end 14 | end 15 | end 16 | 17 | describe 'invalid ensure' do 18 | [ 19 | nil, 20 | [nil], 21 | [nil, nil], 22 | { 'foo' => 'bar' }, 23 | {}, 24 | '', 25 | 's', 26 | 'mailto:', 27 | 'blah', 28 | '199', 29 | 600, 30 | 1_000, 31 | ].each do |value| 32 | describe value.inspect do 33 | it { is_expected.not_to allow_value(value) } 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/type_aliases/firewall.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Firewall' do 6 | describe 'valid firewall' do 7 | %w[ 8 | iptables 9 | firewalld 10 | ].each do |value| 11 | describe value.inspect do 12 | it { is_expected.to allow_value(value) } 13 | end 14 | end 15 | end 16 | 17 | describe 'invalid firewall' do 18 | [ 19 | nil, 20 | [nil], 21 | [nil, nil], 22 | { 'foo' => 'bar' }, 23 | {}, 24 | '', 25 | 's', 26 | 'mailto:', 27 | 'blah', 28 | '199', 29 | 600, 30 | 1_000, 31 | ].each do |value| 32 | describe value.inspect do 33 | it { is_expected.not_to allow_value(value) } 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/type_aliases/auth_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Node_auth' do 6 | describe 'valid node_auth' do 7 | %w[ 8 | cert 9 | token 10 | bootstrap 11 | ].each do |value| 12 | describe value.inspect do 13 | it { is_expected.to allow_value(value) } 14 | end 15 | end 16 | end 17 | 18 | describe 'invalid node_auth' do 19 | [ 20 | nil, 21 | [nil], 22 | [nil, nil], 23 | { 'foo' => 'bar' }, 24 | {}, 25 | '', 26 | 's', 27 | 'mailto:', 28 | 'blah', 29 | '199', 30 | 600, 31 | 1_000, 32 | ].each do |value| 33 | describe value.inspect do 34 | it { is_expected.not_to allow_value(value) } 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/type_aliases/node_auth_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Node_auth' do 6 | describe 'valid node_auth' do 7 | %w[ 8 | cert 9 | token 10 | bootstrap 11 | ].each do |value| 12 | describe value.inspect do 13 | it { is_expected.to allow_value(value) } 14 | end 15 | end 16 | end 17 | 18 | describe 'invalid node_auth' do 19 | [ 20 | nil, 21 | [nil], 22 | [nil, nil], 23 | { 'foo' => 'bar' }, 24 | {}, 25 | '', 26 | 's', 27 | 'mailto:', 28 | 'blah', 29 | '199', 30 | 600, 31 | 1_000, 32 | ].each do |value| 33 | describe value.inspect do 34 | it { is_expected.not_to allow_value(value) } 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/type_aliases/proxy_auth_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Proxy_auth' do 6 | describe 'valid proxy_auth' do 7 | %w[ 8 | cert 9 | token 10 | incluster 11 | ].each do |value| 12 | describe value.inspect do 13 | it { is_expected.to allow_value(value) } 14 | end 15 | end 16 | end 17 | 18 | describe 'invalid proxy_auth' do 19 | [ 20 | nil, 21 | [nil], 22 | [nil, nil], 23 | { 'foo' => 'bar' }, 24 | {}, 25 | '', 26 | 's', 27 | 'mailto:', 28 | 'blah', 29 | '199', 30 | 600, 31 | 1_000, 32 | ].each do |value| 33 | describe value.inspect do 34 | it { is_expected.not_to allow_value(value) } 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/type_aliases/version_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Version' do 6 | describe 'valid version' do 7 | [ 8 | '1.25.0', 9 | '1.13.4', 10 | '1.999.4', 11 | ].each do |value| 12 | describe value.inspect do 13 | it { is_expected.to allow_value(value) } 14 | end 15 | end 16 | end 17 | 18 | describe 'invalid version' do 19 | [ 20 | nil, 21 | [nil], 22 | [nil, nil], 23 | { 'foo' => 'bar' }, 24 | {}, 25 | '', 26 | '1.20', 27 | '5.5.5.5', 28 | 'blah', 29 | '199', 30 | 600, 31 | 1_000, 32 | ].each do |value| 33 | describe value.inspect do 34 | it { is_expected.not_to allow_value(value) } 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/type_aliases/container_runtimes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Container_runtimes' do 6 | describe 'valid container runtime' do 7 | %w[ 8 | containerd 9 | crio 10 | ].each do |value| 11 | describe value.inspect do 12 | it { is_expected.to allow_value(value) } 13 | end 14 | end 15 | end 16 | 17 | describe 'invalid container runtime' do 18 | [ 19 | nil, 20 | [nil], 21 | [nil, nil], 22 | { 'foo' => 'bar' }, 23 | {}, 24 | '', 25 | 's', 26 | 'mailto:', 27 | 'blah', 28 | '199', 29 | 600, 30 | 1_000, 31 | ].each do |value| 32 | describe value.inspect do 33 | it { is_expected.not_to allow_value(value) } 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/type_aliases/quantity_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Quantity' do 6 | describe 'valid quantity' do 7 | [ 8 | '1', 9 | '-52.3', 10 | '200Mi', 11 | '20m', 12 | '3.33T', 13 | '+4e5', 14 | ].each do |value| 15 | describe value.inspect do 16 | it { is_expected.to allow_value(value) } 17 | end 18 | end 19 | end 20 | 21 | describe 'invalid quantity' do 22 | [ 23 | nil, 24 | [nil], 25 | [nil, nil], 26 | { 'foo' => 'bar' }, 27 | {}, 28 | '', 29 | 'm', 30 | 'Mi', 31 | '.5s', 32 | 'blah', 33 | ].each do |value| 34 | describe value.inspect do 35 | it { is_expected.not_to allow_value(value) } 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | source ENV['GEM_SOURCE'] || 'https://rubygems.org' 5 | 6 | group :test do 7 | gem 'voxpupuli-test', '~> 13.0', :require => false 8 | gem 'puppet_metadata', '~> 5.0', :require => false 9 | end 10 | 11 | group :development do 12 | gem 'guard-rake', :require => false 13 | gem 'overcommit', '>= 0.39.1', :require => false 14 | end 15 | 16 | group :system_tests do 17 | gem 'voxpupuli-acceptance', '~> 4.0', :require => false 18 | end 19 | 20 | group :release do 21 | gem 'voxpupuli-release', '~> 5.0', :require => false 22 | end 23 | 24 | gem 'rake', :require => false 25 | 26 | gem 'openvox', ENV.fetch('OPENVOX_GEM_VERSION', [">= 7", "< 9"]), :require => false, :groups => [:test] 27 | 28 | # vim: syntax=ruby 29 | -------------------------------------------------------------------------------- /spec/type_aliases/extended_key_usage_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Extended_key_usage' do 6 | describe 'valid extended_key_usage' do 7 | [ 8 | ['clientAuth'], 9 | ['serverAuth'], 10 | ].each do |value| 11 | describe value.inspect do 12 | it { is_expected.to allow_value(value) } 13 | end 14 | end 15 | end 16 | 17 | describe 'invalid extended_key_usage' do 18 | [ 19 | nil, 20 | [nil], 21 | [nil, nil], 22 | { 'foo' => 'bar' }, 23 | {}, 24 | '', 25 | 's', 26 | 'mailto:', 27 | 'blah', 28 | '199', 29 | 600, 30 | 1_000, 31 | ].each do |value| 32 | describe value.inspect do 33 | it { is_expected.not_to allow_value(value) } 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/type_aliases/proxy_method_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Proxy_method' do 6 | describe 'valid proxy_method' do 7 | [ 8 | true, 9 | false, 10 | 'on-node', 11 | 'in-cluster' 12 | ].each do |value| 13 | describe value.inspect do 14 | it { is_expected.to allow_value(value) } 15 | end 16 | end 17 | end 18 | 19 | describe 'invalid proxy_auth' do 20 | [ 21 | nil, 22 | [nil], 23 | [nil, nil], 24 | { 'foo' => 'bar' }, 25 | {}, 26 | '', 27 | 's', 28 | 'mailto:', 29 | 'blah', 30 | '199', 31 | 600, 32 | 1_000, 33 | ].each do |value| 34 | describe value.inspect do 35 | it { is_expected.not_to allow_value(value) } 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/type_aliases/uri_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::URI' do 6 | describe 'valid uri' do 7 | [ 8 | 'unix:///tmp/application.socket', 9 | 'tcp://192.168.0.1:88', 10 | 'http://example.com', 11 | ].each do |value| 12 | describe value.inspect do 13 | it { is_expected.to allow_value(value) } 14 | end 15 | end 16 | end 17 | 18 | describe 'invalid uri' do 19 | [ 20 | nil, 21 | [nil], 22 | [nil, nil], 23 | { 'foo' => 'bar' }, 24 | {}, 25 | '', 26 | 's', 27 | 'mailto:', 28 | 'blah', 29 | '199', 30 | 600, 31 | 1_000, 32 | ].each do |value| 33 | describe value.inspect do 34 | it { is_expected.not_to allow_value(value) } 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /manifests/server/tls/k8s_sign.pp: -------------------------------------------------------------------------------- 1 | # @summary Signs pending CSR requests for bootstrapping clients 2 | # 3 | # TODO - This should probably be done as a service next to the apiservers 4 | # @param kubeconfig Path to the kubeconfig file 5 | # 6 | define k8s::server::tls::k8s_sign ( 7 | $kubeconfig = '/root/.kube/config', 8 | ) { 9 | $exec_command = [ 10 | "kubectl --kubeconfig='${kubeconfig}' get csr", 11 | "grep 'system:node:${name}'", 12 | 'grep Pending', 13 | "awk '{print \$1}'", 14 | "xargs -r kubectl --kubeconfig='${kubeconfig}' certificate approve", 15 | ].join(' | ') 16 | 17 | exec { "Sign ${name} cert": 18 | path => $facts['path'], 19 | command => $exec_command, 20 | onlyif => "kubectl --kubeconfig='${kubeconfig}' get csr | grep 'system:node:${name}' | grep Pending", 21 | require => 'File[/usr/bin/kubectl]', 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spec/type_aliases/node_role_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Node_role' do 6 | describe 'valid node_role' do 7 | %w[ 8 | node 9 | server 10 | control-plane 11 | etcd-replica 12 | none 13 | ].each do |value| 14 | describe value.inspect do 15 | it { is_expected.to allow_value(value) } 16 | end 17 | end 18 | end 19 | 20 | describe 'invalid node_role' do 21 | [ 22 | nil, 23 | [nil], 24 | [nil, nil], 25 | { 'foo' => 'bar' }, 26 | {}, 27 | '', 28 | 's', 29 | 'mailto:', 30 | 'blah', 31 | '199', 32 | 600, 33 | 1_000, 34 | ].each do |value| 35 | describe value.inspect do 36 | it { is_expected.not_to allow_value(value) } 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/type_aliases/duration_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Duration' do 6 | describe 'valid duration' do 7 | [ 8 | '300ms', 9 | '-1.5h', 10 | '2h45m', 11 | '1h10m10s', 12 | '1µs', 13 | '1us', 14 | ].each do |value| 15 | describe value.inspect do 16 | it { is_expected.to allow_value(value) } 17 | end 18 | end 19 | end 20 | 21 | describe 'invalid duration' do 22 | [ 23 | nil, 24 | [nil], 25 | [nil, nil], 26 | { 'foo' => 'bar' }, 27 | {}, 28 | '', 29 | 's', 30 | '.5s', 31 | 'blah', 32 | '199', 33 | 600, 34 | 1_000, 35 | ].each do |value| 36 | describe value.inspect do 37 | it { is_expected.not_to allow_value(value) } 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /.github/workflows/prepare_release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | name: 'Prepare Release' 6 | 7 | on: 8 | workflow_dispatch: 9 | inputs: 10 | version: 11 | description: 'Module version to be released. Must be a valid semver string without leading v. (1.2.3)' 12 | required: false 13 | 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | 18 | jobs: 19 | release_prep: 20 | uses: 'voxpupuli/gha-puppet/.github/workflows/prepare_release.yml@v3' 21 | with: 22 | version: ${{ github.event.inputs.version }} 23 | allowed_owner: 'voxpupuli' 24 | secrets: 25 | # Configure secrets here: 26 | # https://docs.github.com/en/actions/security-guides/encrypted-secrets 27 | github_pat: '${{ secrets.PCCI_PAT_RELEASE_PREP }}' 28 | -------------------------------------------------------------------------------- /spec/functions/k8s/format_arguments_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::format_arguments' do 6 | it { is_expected.not_to eq(nil) } 7 | it { is_expected.to run.with_params.and_raise_error(ArgumentError) } 8 | it { is_expected.to run.with_params('one').and_raise_error(ArgumentError) } 9 | it { is_expected.to run.with_params({ 'one' => 1 }).and_return(['--one=1']) } 10 | it { is_expected.to run.with_params({ 'str' => 'value' }).and_return(['--str=value']) } 11 | it { is_expected.to run.with_params({ 'bool' => true, 'arr' => %w[arg arg arg] }).and_return(['--bool=true', '--arr=arg,arg,arg']) } 12 | it { is_expected.to run.with_params({ 'hash' => { 'one' => 1, 'two' => 2 } }).and_return(['--hash=one=1,two=2']) } 13 | it { is_expected.to run.with_params({ 'complex_param' => false }).and_return(['--complex-param=false']) } 14 | end 15 | -------------------------------------------------------------------------------- /spec/functions/k8s/ip_in_cidr.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::ip_in_cidr' do 6 | it { is_expected.not_to eq(nil) } 7 | it { is_expected.to run.with_params.and_raise_error(ArgumentError) } 8 | it { is_expected.to run.with_params('one').and_raise_error(ArgumentError) } 9 | it { is_expected.to run.with_params('172.0.0.0/8').and_return('172.0.0.1') } 10 | it { is_expected.to run.with_params('172.0.0.0/8', 'first').and_return('172.0.0.1') } 11 | it { is_expected.to run.with_params('172.0.0.0/8', 'second').and_return('172.0.0.2') } 12 | it { is_expected.to run.with_params('172.0.0.0/8', 600).and_return('172.0.2.88') } 13 | it { is_expected.to run.with_params('fe80:dead:beef::/64').and_return('fe80:dead:beef::1') } 14 | it { is_expected.to run.with_params('fe80:dead:beef::/64', 600).and_return('fe80:dead:beef::258') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/type_aliases/native_packaging_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Native_packaging' do 6 | describe 'valid native_packaging' do 7 | %w[ 8 | package 9 | tarball 10 | loose 11 | hyperkube 12 | manual 13 | ].each do |value| 14 | describe value.inspect do 15 | it { is_expected.to allow_value(value) } 16 | end 17 | end 18 | end 19 | 20 | describe 'invalid native_packaging' do 21 | [ 22 | nil, 23 | [nil], 24 | [nil, nil], 25 | { 'foo' => 'bar' }, 26 | {}, 27 | '', 28 | 's', 29 | 'mailto:', 30 | 'blah', 31 | '199', 32 | 600, 33 | 1_000, 34 | ].each do |value| 35 | describe value.inspect do 36 | it { is_expected.not_to allow_value(value) } 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /examples/simple_setup/Readme.md: -------------------------------------------------------------------------------- 1 | # Simple Setup 2 | 3 | With this two profiles one can setup a simple cluster with bridged network. 4 | This are traditional profile classes which wrap a module and pass some data to it. 5 | You also could load the class directly and pass the data directly to it via hiera in its own namespace. 6 | 7 | The control plane can also act as a worker but for this example it is disabled. 8 | Have also a look at the data. With this the CA will be auto-generated and deployed. 9 | 10 | This example only works if a puppetdb is present. 11 | On an empty puppetdb or very first run you might have to run puppet twice on the control plane. 12 | 13 | ``` 14 | examples/simple_setup 15 | ├── Readme.md 16 | ├── data 17 | │   ├── common.yaml 18 | │   └── nodes 19 | │   ├── controller.yaml 20 | │   └── worker.yaml 21 | └── manifests 22 | ├── controller.pp 23 | └── worker.pp 24 | ``` 25 | -------------------------------------------------------------------------------- /spec/type_aliases/bootstrap_token_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Bootstrap_token' do 6 | describe 'valid bootstrap_token' do 7 | %w[ 8 | 0000000000000000 9 | 0123456788abcdef 10 | st62jgvado5wmxq0 11 | eurx5qsdwf9z3v7t 12 | ].each do |value| 13 | describe value.inspect do 14 | it { is_expected.to allow_value(value) } 15 | end 16 | end 17 | end 18 | 19 | describe 'invalid bootstrap_token' do 20 | [ 21 | nil, 22 | [nil], 23 | [nil, nil], 24 | { 'foo' => 'bar' }, 25 | {}, 26 | '', 27 | 's', 28 | 'mailto:', 29 | '58QLY1G3RASKBJTC', 30 | 'blah', 31 | '199', 32 | 600, 33 | 1_000, 34 | ].each do |value| 35 | describe value.inspect do 36 | it { is_expected.not_to allow_value(value) } 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Managed by modulesync - DO NOT EDIT 4 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 5 | 6 | # puppetlabs_spec_helper will set up coverage if the env variable is set. 7 | # We want to do this if lib exists and it hasn't been explicitly set. 8 | ENV['COVERAGE'] ||= 'yes' if Dir.exist?(File.expand_path('../lib', __dir__)) 9 | 10 | require 'voxpupuli/test/spec_helper' 11 | 12 | RSpec.configure do |c| 13 | c.facterdb_string_keys = true 14 | end 15 | 16 | add_mocked_facts! 17 | 18 | if File.exist?(File.join(__dir__, 'default_module_facts.yml')) 19 | facts = YAML.safe_load(File.read(File.join(__dir__, 'default_module_facts.yml'))) 20 | facts&.each do |name, value| 21 | add_custom_fact name.to_sym, value 22 | end 23 | end 24 | 25 | Dir['./spec/support/**/*.rb'].sort.each { |f| require f } 26 | Dir['./spec/support/spec/**/*.rb'].sort.each { |f| require f } 27 | -------------------------------------------------------------------------------- /spec/type_aliases/tls_altnames.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::TLS_altnames' do 6 | describe 'valid TLS altnames' do 7 | [ 8 | [], 9 | ['1.2.3.4'], 10 | ['2001:db8:3333:4444:5555:6666:7777:8888'], 11 | ['::1'], 12 | ['www.example.com'], 13 | ['fullname'], 14 | ['127.0.0.1', '::1', 'localhost'] 15 | ].each do |value| 16 | describe value.inspect do 17 | it { is_expected.to allow_value(value) } 18 | end 19 | end 20 | end 21 | 22 | describe 'invalid TLS altnames' do 23 | [ 24 | [nil], 25 | nil, 26 | { 'foo' => 'bar' }, 27 | {}, 28 | '', 29 | [''], 30 | ['mailto:'], 31 | ['199'], 32 | 600, 33 | 1_000, 34 | ].each do |value| 35 | describe value.inspect do 36 | it { is_expected.not_to allow_value(value) } 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/classes/server/resources_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::resources' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | function assert_private() {} 9 | 10 | include ::k8s 11 | class { '::k8s::server': 12 | manage_etcd => true, 13 | manage_certs => true, 14 | manage_components => false, 15 | manage_resources => false, 16 | node_on_server => false, 17 | } 18 | PUPPET 19 | end 20 | 21 | on_supported_os.each do |os, os_facts| 22 | context "on #{os}" do 23 | let(:facts) { os_facts } 24 | 25 | it { is_expected.to compile } 26 | 27 | it { is_expected.to contain_k8s__server__bootstrap_token('puppet') } 28 | it { is_expected.to contain_kubectl_apply('system-bootstrap-node-bootstrapper') } 29 | it { is_expected.to contain_kubectl_apply('system-bootstrap-approve-node-client-csr') } 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/type_aliases/cidr_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::CIDR' do 6 | describe 'valid CIDR' do 7 | [ 8 | '1.2.3.4/8', 9 | '2001:db8:3333:4444:5555:6666:7777:8888/32', 10 | ['1.2.3.4/8'], 11 | ['2001:db8:3333:4444:5555:6666:7777:8888/32'], 12 | ['1.2.3.4/8', '2001:db8:3333:4444:5555:6666:7777:8888/32'], 13 | ].each do |value| 14 | describe value.inspect do 15 | it { is_expected.to allow_value(value) } 16 | end 17 | end 18 | end 19 | 20 | describe 'invalid CIDR' do 21 | [ 22 | nil, 23 | [nil], 24 | [nil, nil], 25 | [], 26 | { 'foo' => 'bar' }, 27 | {}, 28 | '', 29 | 's', 30 | 'mailto:', 31 | 'blah', 32 | '199', 33 | 600, 34 | 1_000, 35 | ].each do |value| 36 | describe value.inspect do 37 | it { is_expected.not_to allow_value(value) } 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /examples/cilium/Readme.md: -------------------------------------------------------------------------------- 1 | # Cilium Setup 2 | 3 | It is exactly the same as in the [Simple Setup](../simple_setup/), but we set some different data. 4 | 5 | Make sure if this is the default or you explicitly set this data. 6 | We don't want any other cni to be installed in the first place. 7 | 8 | ```yaml 9 | k8s::node::manage_simple_cni: false 10 | k8s::server::resources::manage_flannel: false 11 | ``` 12 | 13 | The nodes will be in NotReady state at the beginning, because we have no network installed yet. 14 | After puppet has finished, cilium binary can be downloaded and executed. 15 | 16 | see: https://docs.cilium.io/en/v1.13/gettingstarted/k8s-install-default/ 17 | 18 | Execute this on one of the control plane nodes: 19 | 20 | ```bash 21 | # all defaults 22 | cilium install 23 | 24 | # custom setup 25 | cilium install --helm-values /path/to/helm-values.yaml 26 | ``` 27 | 28 | Cilium will completly replace kube-proxy. 29 | It is not needed anymore after installing cilium and can be de-installed. 30 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Managed by modulesync - DO NOT EDIT 3 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 4 | 5 | # https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes 6 | 7 | changelog: 8 | exclude: 9 | labels: 10 | - duplicate 11 | - invalid 12 | - modulesync 13 | - question 14 | - skip-changelog 15 | - wont-fix 16 | - wontfix 17 | 18 | categories: 19 | - title: Breaking Changes 🛠 20 | labels: 21 | - backwards-incompatible 22 | 23 | - title: New Features 🎉 24 | labels: 25 | - enhancement 26 | 27 | - title: Bug Fixes 🐛 28 | labels: 29 | - bug 30 | 31 | - title: Documentation Updates 📚 32 | labels: 33 | - documentation 34 | - docs 35 | 36 | - title: Dependency Updates ⬆️ 37 | labels: 38 | - dependencies 39 | 40 | - title: Other Changes 41 | labels: 42 | - "*" 43 | -------------------------------------------------------------------------------- /spec/type_aliases/ip_addresses_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::IP_addresses' do 6 | describe 'valid IP addresses' do 7 | [ 8 | '1.2.3.4', 9 | '2001:db8:3333:4444:5555:6666:7777:8888', 10 | ['1.2.3.4'], 11 | ['2001:db8:3333:4444:5555:6666:7777:8888'], 12 | ['1.2.3.4', '2001:db8:3333:4444:5555:6666:7777:8888'], 13 | ].each do |value| 14 | describe value.inspect do 15 | it { is_expected.to allow_value(value) } 16 | end 17 | end 18 | end 19 | 20 | describe 'invalid IP addresses' do 21 | [ 22 | nil, 23 | [nil], 24 | [nil, nil], 25 | [], 26 | { 'foo' => 'bar' }, 27 | {}, 28 | '', 29 | [''], 30 | ['s'], 31 | ['mailto:'], 32 | ['blah'], 33 | ['199'], 34 | 600, 35 | 1_000, 36 | ].each do |value| 37 | describe value.inspect do 38 | it { is_expected.not_to allow_value(value) } 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | 4 | begin 5 | require 'voxpupuli/test/rake' 6 | rescue LoadError 7 | # only available if gem group test is installed 8 | end 9 | 10 | begin 11 | require 'voxpupuli/acceptance/rake' 12 | rescue LoadError 13 | # only available if gem group acceptance is installed 14 | end 15 | 16 | begin 17 | require 'voxpupuli/release/rake_tasks' 18 | rescue LoadError 19 | # only available if gem group releases is installed 20 | else 21 | GCGConfig.user = 'voxpupuli' 22 | GCGConfig.project = 'puppet-k8s' 23 | end 24 | 25 | desc "Run main 'test' task and report merged results to coveralls" 26 | task test_with_coveralls: [:test] do 27 | if Dir.exist?(File.expand_path('../lib', __FILE__)) 28 | require 'coveralls/rake/task' 29 | Coveralls::RakeTask.new 30 | Rake::Task['coveralls:push'].invoke 31 | else 32 | puts 'Skipping reporting to coveralls. Module has no lib dir' 33 | end 34 | end 35 | 36 | # vim: syntax=ruby 37 | -------------------------------------------------------------------------------- /spec/classes/server/wait_online_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::wait_online' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | function assert_private() {} 9 | 10 | include ::k8s 11 | class { '::k8s::server': 12 | manage_etcd => false, 13 | manage_certs => true, 14 | manage_components => false, 15 | manage_resources => false, 16 | node_on_server => false, 17 | } 18 | class { '::k8s::server::apiserver': 19 | etcd_servers => [], 20 | } 21 | PUPPET 22 | end 23 | 24 | on_supported_os.each do |os, os_facts| 25 | context "on #{os}" do 26 | let(:facts) { os_facts } 27 | 28 | it { is_expected.to compile } 29 | 30 | it do 31 | is_expected.to contain_exec('k8s apiserver wait online'). 32 | that_requires('Kubeconfig[/root/.kube/config]'). 33 | that_requires('K8s::Binary[kubectl]'). 34 | that_requires('Service[kube-apiserver]') 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /manifests/server/wait_online.pp: -------------------------------------------------------------------------------- 1 | # @summary Creates a dummy exec to allow deferring applies until the Kubernetes API server has started 2 | # 3 | # @param tries Number of retries 4 | # @param try_sleep Sleep time in seconds 5 | # @param timeout Execution timeout in seconds (0 to disable) 6 | class k8s::server::wait_online ( 7 | Integer $tries = 15, 8 | Integer $timeout = 5, 9 | Integer $try_sleep = 2, 10 | ) { 11 | # Wait up to $tries * ( $timeout + $try_sleep) seconds for kube-apiserver to start 12 | exec { 'k8s apiserver wait online': 13 | command => 'kubectl --kubeconfig /root/.kube/config version', 14 | path => $facts['path'], 15 | refreshonly => true, 16 | tries => $tries, 17 | try_sleep => $try_sleep, 18 | timeout => $timeout, 19 | } 20 | 21 | # Require possibly managed components before checking online state 22 | Kubeconfig <| title == '/root/.kube/config' |> -> Exec['k8s apiserver wait online'] 23 | K8s::Binary <| title == 'kubectl' |> -> Exec['k8s apiserver wait online'] 24 | Service <| title == 'kube-apiserver' |> ~> Exec['k8s apiserver wait online'] 25 | } 26 | -------------------------------------------------------------------------------- /spec/classes/node/kubelet_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::node::kubelet' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | function assert_private() {} 9 | 10 | include ::k8s 11 | class { '::k8s::node': 12 | manage_kubelet => false, 13 | manage_proxy => false, 14 | } 15 | PUPPET 16 | end 17 | 18 | on_supported_os.each do |os, os_facts| 19 | context "on #{os}" do 20 | let(:facts) { os_facts } 21 | 22 | it { is_expected.to compile } 23 | it { is_expected.to contain_kmod__load('overlay') } 24 | it { is_expected.to contain_kmod__load('br_netfilter') } 25 | it { is_expected.to contain_sysctl('net.bridge.bridge-nf-call-iptables').with_ensure('present').with_value('1') } 26 | it { is_expected.to contain_sysctl('net.bridge.bridge-nf-call-ip6tables').with_ensure('present').with_value('1') } 27 | it { is_expected.to contain_sysctl('net.ipv4.ip_forward').with_ensure('present').with_value('1') } 28 | it { is_expected.to contain_sysctl('net.ipv6.conf.all.forwarding').with_ensure('present').with_value('1') } 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/classes/install/cni_plugins_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::install::cni_plugins' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | include k8s 9 | PUPPET 10 | end 11 | 12 | on_supported_os.each do |os, os_facts| 13 | context "on #{os}" do 14 | let(:facts) { os_facts } 15 | 16 | it { is_expected.to compile } 17 | it { is_expected.to contain_file('/opt/cni') } 18 | 19 | context 'when method is tarball' do 20 | let(:params) do 21 | { 22 | method: 'tarball', 23 | version: 'v1.0.0' 24 | } 25 | end 26 | 27 | it { is_expected.to contain_archive('cni-plugins').with_extract_path('/opt/k8s/cni-v1.0.0') } 28 | 29 | context 'without storage fact' do 30 | it { is_expected.not_to contain_exec('Retain custom CNI binaries') } 31 | end 32 | 33 | context 'with storage fact' do 34 | let(:facts) { os_facts.merge(cni_plugins_version: 'v0.0.0') } 35 | 36 | it { is_expected.to contain_exec('Retain custom CNI binaries') } 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/puppet/functions/k8s/format_arguments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Formats a hash of arguments into something that can be passed to a kubernetes application 4 | Puppet::Functions.create_function(:'k8s::format_arguments') do 5 | # @param arguments A hash of arguments to format 6 | # 7 | # @return [Array[String]] An array of formatted kubernetes arguments 8 | dispatch :k8s_format_arguments do 9 | param 'Hash[String,Data]', :arguments 10 | return_type 'Array[String]' 11 | end 12 | 13 | def k8s_format_value(value) 14 | case value 15 | when String, Numeric, TrueClass, FalseClass 16 | value 17 | when Array 18 | value.map { |data| k8s_format_value(data) }.join(',') 19 | when Hash 20 | value.map { |key, data| "#{key.tr('_', '-')}=#{k8s_format_value(data)}" }.join(',') 21 | else 22 | raise ArgumentError, "Unable to format #{value.inspect} (#{value.class})" 23 | end 24 | end 25 | 26 | def k8s_format_arguments(arguments) 27 | formatted = arguments.map do |argument, value| 28 | next if value.nil? 29 | 30 | "--#{argument.tr('_', '-')}=#{k8s_format_value(value)}" 31 | end 32 | formatted.compact 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/classes/server/resources/coredns_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::resources::coredns' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | function assert_private() {} 9 | 10 | include ::k8s 11 | class { '::k8s::server': 12 | manage_etcd => true, 13 | manage_certs => true, 14 | manage_components => false, 15 | manage_resources => false, 16 | node_on_server => false, 17 | } 18 | include ::k8s::server::resources 19 | PUPPET 20 | end 21 | 22 | on_supported_os.each do |os, os_facts| 23 | context "on #{os}" do 24 | let(:facts) { os_facts } 25 | 26 | it { is_expected.to compile } 27 | 28 | it { is_expected.to contain_kubectl_apply('coredns ServiceAccount') } 29 | it { is_expected.to contain_kubectl_apply('coredns ClusterRole') } 30 | it { is_expected.to contain_kubectl_apply('coredns ClusterRoleBinding') } 31 | it { is_expected.to contain_kubectl_apply('coredns ConfigMap') } 32 | it { is_expected.to contain_kubectl_apply('coredns Deployment') } 33 | it { is_expected.to contain_kubectl_apply('coredns Service') } 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/classes/server/tls_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::tls' do 6 | let(:params) do 7 | { 8 | generate_ca: true, 9 | manage_certs: true 10 | } 11 | end 12 | let(:pre_condition) do 13 | <<~PUPPET 14 | function assert_private() {} 15 | 16 | include ::k8s 17 | class { '::k8s::server': 18 | manage_etcd => false, 19 | manage_certs => false, 20 | manage_components => false, 21 | manage_resources => false, 22 | node_on_server => false, 23 | } 24 | PUPPET 25 | end 26 | 27 | on_supported_os.each do |os, os_facts| 28 | context "on #{os}" do 29 | let(:facts) { os_facts } 30 | 31 | it { is_expected.to compile } 32 | 33 | %w[kube-ca aggregator-ca].each do |ca| 34 | it { is_expected.to contain_k8s__server__tls__ca(ca) } 35 | end 36 | 37 | %w[ 38 | kube-apiserver front-proxy-client 39 | apiserver-kubelet-client kube-controller-manager 40 | kube-scheduler kube-proxy node admin 41 | ].each do |cert| 42 | it { is_expected.to contain_k8s__server__tls__cert(cert) } 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/type_aliases/timestamp_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'K8s::Timestamp' do 6 | describe 'valid RFC3339 timestamp' do 7 | [ 8 | '2018-04-11T13:57:56Z', 9 | '1999-12-31T23:59:59.999+02:00', 10 | '1990-01-01T00:00:00Z', 11 | '1990-01-01t00:00:00+00:30', 12 | '1990-01-01t00:00:00-01:30', 13 | ].each do |value| 14 | describe value.inspect do 15 | it { is_expected.to allow_value(value) } 16 | end 17 | end 18 | end 19 | 20 | describe 'invalid RFC3339 timestamp' do 21 | [ 22 | nil, 23 | [nil], 24 | [nil, nil], 25 | { 'foo' => 'bar' }, 26 | {}, 27 | '', 28 | '2018-04-11 13:57:56', 29 | '2018-04-11T13:57:56', 30 | '2018-04-11Z13:57:56', 31 | '2990-13-11T13:57:56Z', 32 | '2990-13-11T13:57:56Z+01:00', 33 | '1999-12-32T23:59:59.999+02:00', 34 | '1979-05-05T24:00:00Z', 35 | '1979-05-05T05:60:00Z', 36 | '1979-05-05T05:21:61Z', 37 | '1979-05-05T01:00Z', 38 | 199, 39 | 1_000, 40 | ].each do |value| 41 | describe value.inspect do 42 | it { is_expected.not_to allow_value(value) } 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/classes/server/resources/kube_proxy_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::resources::kube_proxy' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | function assert_private() {} 9 | 10 | class { '::k8s': 11 | version => '1.23.4', 12 | } 13 | class { '::k8s::server': 14 | manage_etcd => true, 15 | manage_certs => true, 16 | manage_components => false, 17 | manage_resources => false, 18 | node_on_server => false, 19 | } 20 | include ::k8s::server::resources 21 | PUPPET 22 | end 23 | 24 | let(:content) { Psych.load(File.read('spec/fixtures/files/resources/kube-proxy.yaml')) } 25 | 26 | on_supported_os.each do |os, os_facts| 27 | context "on #{os}" do 28 | let(:facts) { os_facts } 29 | 30 | it { is_expected.to compile } 31 | 32 | it { is_expected.to contain_kubectl_apply('kube-proxy ServiceAccount') } 33 | it { is_expected.to contain_kubectl_apply('kube-proxy ClusterRoleBinding') } 34 | it { is_expected.to contain_kubectl_apply('kube-proxy ConfigMap') } 35 | 36 | it { is_expected.to contain_kubectl_apply('kube-proxy DaemonSet').with_content(content) } 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /manifests/node/simple_cni.pp: -------------------------------------------------------------------------------- 1 | # Class: k8s::node::simple_cni 2 | # 3 | # @summary Provide a simple bridged standard network interface. 4 | # For basic usage if one does not have flannel, cilium, calico or something else yet. 5 | # Uses the cni-plugins bridge binary to create a bridge interface to connect the containers 6 | # 7 | # @param pod_cidr cidr for pods in the network 8 | class k8s::node::simple_cni ( 9 | K8s::CIDR $pod_cidr = $k8s::cluster_cidr, 10 | ) { 11 | $bridge = { 12 | cniVersion => '0.4.0', 13 | name => 'bridge', 14 | type => 'bridge', 15 | bridge => 'cnio0', 16 | isGateway => true, 17 | ipMasq => true, 18 | ipam => { 19 | type => 'host-local', 20 | ranges => [[{ subnet => $pod_cidr }]], 21 | routes => [{ dst => '0.0.0.0/0' }], 22 | }, 23 | } 24 | 25 | $loopback = { 26 | cniVersion => '0.4.0', 27 | name => 'lo', 28 | type => 'loopback', 29 | } 30 | 31 | file { '/etc/cni/net.d/10-bridge.conf': 32 | ensure => file, 33 | content => $bridge.to_json, 34 | require => File['/etc/cni/net.d'], 35 | } 36 | 37 | file { '/etc/cni/net.d/99-loopback.conf': 38 | ensure => file, 39 | content => $loopback.to_json, 40 | require => File['/etc/cni/net.d'], 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spec/classes/k8s_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s' do 6 | on_supported_os.each do |os, os_facts| 7 | context "on #{os}" do 8 | let(:facts) { os_facts } 9 | let(:hiera_config) { 'hiera-rspec.yaml' } 10 | 11 | it { is_expected.to compile } 12 | 13 | %w[node server etcd-replica].each do |role| 14 | context "with role #{role}" do 15 | let(:params) do 16 | { 17 | role: role, 18 | } 19 | end 20 | 21 | it { is_expected.to compile } 22 | end 23 | end 24 | 25 | context 'With dual-stack' do 26 | it { is_expected.to compile } 27 | 28 | %w[node server].each do |role| 29 | context "with role #{role}" do 30 | let(:params) do 31 | { 32 | cluster_cidr: [ 33 | '10.0.0.0/16', 34 | 'fc00:cafe:42:0::/64', 35 | ], 36 | service_cluster_cidr: [ 37 | '10.1.0.0/24', 38 | 'fc00:cafe:42:1::/64', 39 | ], 40 | role: role, 41 | } 42 | end 43 | 44 | it { is_expected.to compile } 45 | end 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/classes/repo_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::repo' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | class { '::k8s': 9 | manage_repo => false, 10 | } 11 | PUPPET 12 | end 13 | 14 | on_supported_os.each do |os, os_facts| 15 | context "on #{os}" do 16 | let(:facts) { os_facts } 17 | 18 | it { is_expected.to compile } 19 | end 20 | context 'on RedHat/CentOS 7, 8 and 9' do 21 | let(:facts) { os_facts } 22 | if os['family'] == 'RedHat' and os['release']['major'] == '7' 23 | it { is_expected.to contain_yumrepo('libcontainers:stable').with_baseurl => 'https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_7/'} 24 | end 25 | if os['family'] == 'RedHat' and os['release']['major'] == '8' 26 | it { is_expected.to contain_yumrepo('libcontainers:stable').with_baseurl => 'https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_8_Stream/'} 27 | end 28 | if os['family'] == 'RedHat' and os['release']['major'] == '9' 29 | it { is_expected.to contain_yumrepo('libcontainers:stable').with_baseurl => 'https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_9_Stream/'} 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /templates/server/tls/openssl.cnf.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | Array[Enum['clientAuth','serverAuth']] $extended_key_usage = ['clientAuth'], 3 | Hash[String,String] $distinguished_name = {}, 4 | 5 | Array[Stdlib::Fqdn] $dns_altnames, 6 | Array[Stdlib::IP::Address] $ip_altnames, 7 | | -%> 8 | [req] 9 | distinguished_name = req_distinguished_name 10 | req_extensions = v3_req 11 | prompt = no 12 | 13 | [req_distinguished_name] 14 | <%- $distinguished_name.each |$key, $value| { -%> 15 | <%- if $value =~ String[1] { -%> 16 | <%= $key %> = <%= $value %> 17 | <%- } -%> 18 | <%- } -%> 19 | <%- 20 | # countryName = Country 21 | # stateOrProvinceName = State 22 | # localityName = Locality 23 | # organizationName = Org 24 | # organizationalUnitName = Me 25 | # commonName = hostname 26 | -%> 27 | 28 | [v3_req] 29 | basicConstraints = CA:FALSE 30 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 31 | extendedKeyUsage = <%= $extended_key_usage.join(', ') %> 32 | <%- if $dns_altnames =~ Array[Any, 1] { -%> 33 | subjectAltName = @alt_names 34 | 35 | [alt_names] 36 | <%- $dns_altnames.each |Integer $i, $altname| { -%> 37 | DNS.<%= $i + 1 %> = <%= $altname %> 38 | <%- } -%> 39 | <%- $ip_altnames.each |Integer $i, $altname| { -%> 40 | IP.<%= $i + 1 %> = <%= $altname %> 41 | <%- } -%> 42 | <%- } -%> 43 | -------------------------------------------------------------------------------- /spec/defines/server/etcd/member_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::etcd::member' do 6 | let(:title) { 'namevar' } 7 | let(:params) do 8 | { 9 | peer_urls: ['http://localhost:4001'], 10 | } 11 | end 12 | 13 | on_supported_os.each do |os, os_facts| 14 | context "on #{os}" do 15 | let(:facts) { os_facts } 16 | 17 | it { is_expected.to compile } 18 | 19 | it do 20 | is_expected.to contain_exec('Add namevar as member').with( 21 | environment: ['ETCDCTL_API=3'], 22 | command: 'etcdctl member add namevar --peer-urls="http://localhost:4001"', 23 | onlyif: 'etcdctl endpoint health', 24 | unless: %r{etcdctl -w fields member list | grep \\"Name\\" | grep namevar || \s+ etcdctl -w fields member list | grep \\"PeerURL\\" | grep http://localhost:4001}, 25 | path: ['/bin', '/usr/bin', '/usr/local/bin'] 26 | ) 27 | end 28 | 29 | context 'with etcd installed' do 30 | let(:pre_condition) do 31 | <<~PUPPET 32 | service { 'etcd': 33 | ensure => running, 34 | } 35 | PUPPET 36 | end 37 | 38 | it do 39 | is_expected.to contain_exec('Add namevar as member').that_requires('Service[etcd]') 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/puppet/functions/k8s/ip_in_cidr.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Retrieves an IP inside of a CIDR based on an index 4 | Puppet::Functions.create_function(:'k8s::ip_in_cidr') do 5 | # @example In 192.168.0.0/24 6 | # k8s::ip_in_cidr('192.168.0.0/24', 'first') 7 | # # => 192.168.0.1 8 | # k8s::ip_in_cidr('192.168.0.0/24', 'second') 9 | # # => 192.168.0.2 10 | # k8s::ip_in_cidr('192.168.0.0/16', 600) 11 | # # => 192.168.1.244 12 | # 13 | # @param cidr The CIDR to work on 14 | # @param index The index of the IP to retrieve 15 | # 16 | # @return [String] The first IP address in the CIDR 17 | dispatch :ip_in_cidr do 18 | param 'Variant[Stdlib::IP::Address::V4::CIDR, Stdlib::IP::Address::V6::CIDR, Array[Variant[Stdlib::IP::Address::V4::CIDR, Stdlib::IP::Address::V6::CIDR]]]', :cidr 19 | optional_param 'Variant[Enum["first","second"], Integer[1]]', :index 20 | return_type 'String' 21 | end 22 | 23 | require 'ipaddr' 24 | def ip_in_cidr(cidr, index = 1) 25 | cidr = cidr.first if cidr.is_a? Array 26 | 27 | if index.is_a? String 28 | index = 1 if index == 'first' 29 | index = 2 if index == 'second' 30 | end 31 | 32 | ip = IPAddr.new(cidr) 33 | 34 | width = ip.ipv4? ? 32 : 128 35 | raise ArgumentError, 'Index is outside of the CIDR' if index >= 2**(width - ip.prefix) 36 | 37 | index.times { ip = ip.succ } 38 | 39 | ip.to_s 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/defines/server/bootstrap_token_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::bootstrap_token' do 6 | let(:title) { 'nameva' } 7 | let(:params) do 8 | { 9 | kubeconfig: '/root/.kube/config', 10 | secret: sensitive('some0secret0valu') 11 | } 12 | end 13 | 14 | on_supported_os.each do |os, os_facts| 15 | context "on #{os}" do 16 | let(:facts) { os_facts } 17 | 18 | it { is_expected.to compile } 19 | 20 | it do 21 | is_expected.to contain_kubectl_apply('bootstrap-token-nameva').with( 22 | ensure: 'present', 23 | kubeconfig: '/root/.kube/config', 24 | namespace: 'kube-system', 25 | api_version: 'v1', 26 | kind: 'Secret', 27 | content: { 28 | 'type' => 'bootstrap.kubernetes.io/token', 29 | 'data' => { 30 | 'token-id' => 'bmFtZXZh', # 'nameva' 31 | 'token-secret' => 'c29tZTBzZWNyZXQwdmFsdQ==', # 'some0secret0valu' 32 | 'usage-bootstrap-authentication' => 'dHJ1ZQ==', # true 33 | } 34 | } 35 | ) 36 | end 37 | 38 | describe 'with invalid secret' do 39 | let(:params) do 40 | { 41 | kubeconfig: '/root/.kube/config', 42 | secret: sensitive('Something not supported by Kubernetes') 43 | } 44 | end 45 | 46 | it { is_expected.not_to compile } 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/puppet/functions/k8s/format_url.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Formats a download URL for K8s binaries 4 | Puppet::Functions.create_function(:'k8s::format_url') do 5 | # @param url The URL template to format 6 | # @param components A hash of additional arguments 7 | # 8 | # @return String A valid download URL 9 | dispatch :k8s_format_binary do 10 | param 'String[1]', :url 11 | param 'Hash[String,Data]', :components 12 | end 13 | 14 | def k8s_format_binary(url, components) 15 | scope = closure_scope 16 | 17 | arch = scope['facts'].dig('os', 'architecture') 18 | arch = 'amd64' if arch.match? %r{x(86_)?64} 19 | arch = 'arm64' if arch.match? %r{arm64.*|aarch64} 20 | k3s_arch = arch 21 | k3s_arch = 'armhf' if arch.match? %r{armv.*} 22 | k3s_arch = nil if arch == 'amd64' 23 | arch = 'arm' if arch.match? %r{armv.*} 24 | arch = '386' if arch == 'i386' 25 | 26 | underscore_arch_suffix = "_#{arch}" unless arch == 'amd64' 27 | dash_arch_suffix = "-#{arch}" unless arch == 'amd64' 28 | 29 | kernel = scope['facts']['kernel'].downcase 30 | kernel_ext = 'zip' 31 | kernel_ext = 'tar.gz' if kernel == 'linux' 32 | 33 | components = components.transform_keys(&:to_sym).merge( 34 | arch: arch, 35 | underscore_arch_suffix: underscore_arch_suffix, 36 | dash_arch_suffix: dash_arch_suffix, 37 | k3s_arch: k3s_arch, 38 | kernel: kernel, 39 | kernel_ext: kernel_ext 40 | ) 41 | 42 | url % components 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/puppet/provider/kubectl_apply/file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path('../../util/k8s', __dir__) 4 | 5 | # Applies resources as YAML files on disk 6 | Puppet::Type.type(:kubectl_apply).provide(:file) do 7 | attr_reader :resource_diff 8 | 9 | def exists_in_cluster 10 | File.exist? resource[:file] 11 | end 12 | 13 | def exists? 14 | data = file_get 15 | return false unless data 16 | 17 | diff = content_diff(data) 18 | return true if resource[:ensure].to_s == 'absent' || resource[:update] == :false 19 | 20 | diff.empty? 21 | end 22 | 23 | def create 24 | File.write resource[:file], resource_hash.to_json 25 | end 26 | 27 | def destroy 28 | Fileutils.rm resource[:file] 29 | end 30 | 31 | def content_diff(content, store: true) 32 | diff = Puppet::Util::K8s.content_diff(resource[:content], content) 33 | 34 | @resource_diff = diff if store 35 | diff 36 | end 37 | 38 | def resource_hash 39 | hash = resource[:content] 40 | 41 | hash['apiVersion'] = resource[:api_version] 42 | hash['kind'] = resource[:kind] 43 | 44 | metadata = hash['metadata'] ||= {} 45 | metadata['name'] = resource[:resource_name] 46 | metadata['namespace'] = resource[:namespace] if resource[:namespace] 47 | 48 | hash 49 | end 50 | 51 | private 52 | 53 | def file_get 54 | return nil unless File.exist? resource[:file] 55 | 56 | JSON.parse(File.read(resource[:file])) 57 | rescue StandardError 58 | {} 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /templates/service.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | String[1] $name, 3 | String[1] $args_name = "\$${regsubst($name, '-', '_', 'G').upcase()}_ARGS", 4 | String[1] $bin, 5 | Optional[String[1]] $desc = undef, 6 | Optional[String[1]] $doc = undef, 7 | Array[String[1]] $needs = [], 8 | Optional[Stdlib::Unixpath] $dir = undef, 9 | String $target = 'multi-user.target', 10 | 11 | Optional[String[1]] $user = undef, 12 | Optional[String[1]] $group = undef, 13 | Optional[Stdlib::Unixpath] $sysconfig_path = undef, 14 | 15 | Stdlib::Unixpath $bindir = "/opt/k8s/${k8s::version}", 16 | | -%> 17 | [Unit] 18 | Description=<%= pick($desc, $name) %> 19 | <%- if $doc { -%> 20 | Documentation=<%= $doc %> 21 | <%- } -%> 22 | <%- if $needs { -%> 23 | Requires=<%= $needs.join(' ') %> 24 | <%- } -%> 25 | After=network.target <%= $needs.join(' ') %> 26 | 27 | [Service] 28 | <%- if $dir { -%> 29 | WorkingDirectory=<%= $dir %> 30 | <%- } -%> 31 | <%- 32 | $_sysconfig_path = pick($k8s::sysconfig_path, '/etc/sysconfig') 33 | -%> 34 | EnvironmentFile=-<%= $_sysconfig_path %>/kube-common 35 | EnvironmentFile=<%= $_sysconfig_path %>/<%= $name %> 36 | ExecStart=<%= $bindir %>/<%= $bin %> \ 37 | $KUBE_LOGTOSTDERR \ 38 | $KUBE_LOG_LEVEL \ 39 | <%= $args_name %> 40 | <%- if $user { -%> 41 | User=<%= $user %> 42 | <%- } -%> 43 | <%- if $group { -%> 44 | Group=<%= $group %> 45 | <%- } -%> 46 | Restart=on-failure 47 | KillMode=process 48 | CPUAccounting=true 49 | MemoryAccounting=true 50 | 51 | [Install] 52 | WantedBy=<%= $target %> 53 | -------------------------------------------------------------------------------- /spec/defines/server/tls/ca_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::tls::ca' do 6 | let(:title) { 'namevar' } 7 | let(:params) do 8 | { 9 | key: '/tmp/ca.key', 10 | cert: '/tmp/ca.pem', 11 | } 12 | end 13 | 14 | on_supported_os.each do |os, os_facts| 15 | context "on #{os}" do 16 | let(:facts) { os_facts } 17 | 18 | it { is_expected.to compile } 19 | 20 | it do 21 | is_expected.to contain_exec('Create namevar CA key').with( 22 | command: "openssl genrsa -out '/tmp/ca.key' 2048", 23 | unless: "openssl pkey -in '/tmp/ca.key' -text | grep '2048 bit'" 24 | ) 25 | end 26 | 27 | it do 28 | is_expected.to contain_exec('Create namevar CA cert').with( 29 | command: %r{openssl req -x509 -new -nodes -key '/tmp/ca.key'\s+-days '10000' -out '/tmp/ca.pem' -subj '/CN=namevar'}, 30 | unless: "openssl x509 -CA '/tmp/ca.pem' -CAkey '/tmp/ca.key' -in '/tmp/ca.pem' -noout -set_serial 00" 31 | ).that_subscribes_to('File[/tmp/ca.key]') 32 | end 33 | 34 | it do 35 | is_expected.to contain_file('/tmp/ca.key').with( 36 | ensure: 'present', 37 | owner: 'root', 38 | group: 'root', 39 | mode: '0600', 40 | replace: false 41 | ) 42 | end 43 | 44 | it do 45 | is_expected.to contain_file('/tmp/ca.pem').with( 46 | ensure: 'present', 47 | owner: 'root', 48 | group: 'root', 49 | mode: '0644', 50 | replace: false 51 | ) 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | # Managed by modulesync - DO NOT EDIT 2 | # https://voxpupuli.org/docs/updating-files-managed-with-modulesync/ 3 | # 4 | # Hooks are only enabled if you take action. 5 | # 6 | # To enable the hooks run: 7 | # 8 | # ``` 9 | # bundle exec overcommit --install 10 | # # ensure .overcommit.yml does not harm to you and then 11 | # bundle exec overcommit --sign 12 | # ``` 13 | # 14 | # (it will manage the .git/hooks directory): 15 | # 16 | # Examples howto skip a test for a commit or push: 17 | # 18 | # ``` 19 | # SKIP=RuboCop git commit 20 | # SKIP=PuppetLint git commit 21 | # SKIP=RakeTask git push 22 | # ``` 23 | # 24 | # Don't invoke overcommit at all: 25 | # 26 | # ``` 27 | # OVERCOMMIT_DISABLE=1 git commit 28 | # ``` 29 | # 30 | # Read more about overcommit: https://github.com/brigade/overcommit 31 | # 32 | # To manage this config yourself in your module add 33 | # 34 | # ``` 35 | # .overcommit.yml: 36 | # unmanaged: true 37 | # ``` 38 | # 39 | # to your modules .sync.yml config 40 | --- 41 | PreCommit: 42 | RuboCop: 43 | enabled: true 44 | description: 'Runs rubocop on modified files only' 45 | command: ['bundle', 'exec', 'rubocop'] 46 | RakeTarget: 47 | enabled: true 48 | description: 'Runs lint on modified files only' 49 | targets: 50 | - 'lint' 51 | command: ['bundle', 'exec', 'rake'] 52 | YamlSyntax: 53 | enabled: true 54 | JsonSyntax: 55 | enabled: true 56 | TrailingWhitespace: 57 | enabled: true 58 | 59 | PrePush: 60 | RakeTarget: 61 | enabled: true 62 | description: 'Run rake targets' 63 | targets: 64 | - 'validate' 65 | - 'test' 66 | - 'rubocop' 67 | command: ['bundle', 'exec', 'rake'] 68 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2023-08-17 21:34:03 UTC using RuboCop version 1.50.2. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # This cop supports unsafe autocorrection (--autocorrect-all). 11 | Lint/NonAtomicFileOperation: 12 | Exclude: 13 | - 'lib/puppet/provider/kubeconfig/ruby.rb' 14 | 15 | # Offense count: 14 16 | # This cop supports unsafe autocorrection (--autocorrect-all). 17 | RSpec/BeEq: 18 | Exclude: 19 | - 'spec/functions/k8s/format_arguments_spec.rb' 20 | - 'spec/functions/k8s/format_url_spec.rb' 21 | - 'spec/functions/k8s/ip_in_cidr.rb' 22 | - 'spec/support/shared_behaviour.rb' 23 | - 'spec/unit/puppet/provider/kubectl_apply_resource/kubectl_spec.rb' 24 | - 'spec/unit/puppet/type/kubectl_apply_spec.rb' 25 | 26 | # Offense count: 35 27 | # Configuration parameters: . 28 | # SupportedStyles: have_received, receive 29 | RSpec/MessageSpies: 30 | EnforcedStyle: receive 31 | 32 | # Offense count: 21 33 | # Configuration parameters: AllowSubject. 34 | RSpec/MultipleMemoizedHelpers: 35 | Max: 8 36 | 37 | # Offense count: 9 38 | RSpec/StubbedMock: 39 | Exclude: 40 | - 'spec/unit/puppet/provider/kubectl_apply_resource/kubectl_spec.rb' 41 | 42 | # Offense count: 3 43 | # This cop supports unsafe autocorrection (--autocorrect-all). 44 | # Configuration parameters: . 45 | # SupportedStyles: constant, string 46 | RSpec/VerifiedDoubleReference: 47 | EnforcedStyle: string 48 | -------------------------------------------------------------------------------- /examples/simple_setup/manifests/worker.pp: -------------------------------------------------------------------------------- 1 | # Class: profile::k8s::worker 2 | # 3 | # @param role role in the cluster, server, node, none 4 | # @param control_plane_url 5 | # cluster url where the server/nodes connect to. 6 | # this is most likely a load balanced dns with all the controllers in the backend. 7 | # on single head clusters this may be the dns name:port of the controller node. 8 | # @param k8s_version version of kubernetes 9 | # @param puppetdb_discovery whether to use puppetdb or not 10 | # @param manage_firewall whether to manage firewall or not 11 | # @param manage_kube_proxy whether to manage manage_kube_proxy or not, for cilium this is not needed 12 | # @param container_manager set the cri, like cri-o or containerd 13 | # 14 | # lint:ignore:autoloader_layout 15 | class profile::k8s::worker ( 16 | # lint:endignore 17 | Boolean $manage_firewall = true, # k8s-class default: false 18 | Boolean $manage_kube_proxy = true, # k8s-class default: true 19 | Boolean $puppetdb_discovery = true, # k8s-class default: false 20 | K8s::Container_runtimes $container_manager = 'containerd', # k8s-class default: crio 21 | Enum['node'] $role = 'node', # k8s-class default: none 22 | Stdlib::HTTPUrl $control_plane_url = $profile::k8s::controller::control_plane_url, 23 | String[1] $k8s_version = $profile::k8s::controller::k8s_version, 24 | ) { 25 | class { 'k8s': 26 | container_manager => $container_manager, 27 | manage_firewall => $manage_firewall, 28 | manage_kube_proxy => $manage_kube_proxy, 29 | control_plane_url => $control_plane_url, 30 | puppetdb_discovery => $puppetdb_discovery, 31 | role => $role, 32 | version => $k8s_version, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /manifests/server/etcd/member.pp: -------------------------------------------------------------------------------- 1 | # @summary Adds another member to a local etcd cluster 2 | # 3 | # TODO - Convert to native type 4 | # 5 | # @param cluster_ca The cluster CA for the new member 6 | # @param cluster_cert The cluster cert for the new member 7 | # @param cluster_key The cluster key for the new member 8 | # @param cluster_urls The cluster URLs for the new member 9 | # @param peer_urls The peer URLs for the new member 10 | # 11 | define k8s::server::etcd::member ( 12 | Array[String, 1] $peer_urls, 13 | 14 | Optional[Array[Stdlib::HTTPUrl]] $cluster_urls = undef, 15 | Optional[Stdlib::Unixpath] $cluster_ca = undef, 16 | Optional[Stdlib::Unixpath] $cluster_cert = undef, 17 | Optional[Stdlib::Unixpath] $cluster_key = undef, 18 | ) { 19 | $environment = [ 20 | 'ETCDCTL_API=3', 21 | ] + ($cluster_urls ? { 22 | undef => [], 23 | default => [ 24 | "ETCDCTL_ENDPOINTS=${cluster_urls.join(',')}", 25 | ], 26 | }) + ($cluster_ca ? { 27 | undef => [], 28 | default => [ 29 | "ETCDCTL_CACERT=${cluster_ca}", 30 | ], 31 | }) + ($cluster_cert ? { 32 | undef => [], 33 | default => [ 34 | "ETCDCTL_CERT=${cluster_cert}", 35 | ], 36 | }) + ($cluster_key ? { 37 | undef => [], 38 | default => [ 39 | "ETCDCTL_KEY=${cluster_key}", 40 | ], 41 | }) 42 | 43 | Service <| title == 'etcd' |> 44 | -> exec { "Add ${name} as member": 45 | environment => $environment, 46 | command => "etcdctl member add ${name} --peer-urls=\"${peer_urls.join(',')}\"", 47 | onlyif => 'etcdctl endpoint health', 48 | unless => "etcdctl -w fields member list | grep \\\"Name\\\" | grep ${name} || \ 49 | etcdctl -w fields member list | grep \\\"PeerURL\\\" | grep ${peer_urls}", 50 | path => ['/bin', '/usr/bin', '/usr/local/bin'], 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /spec/classes/server/scheduler_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::scheduler' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | function assert_private() {} 9 | 10 | include ::k8s 11 | class { '::k8s::server': 12 | manage_etcd => true, 13 | manage_certs => true, 14 | manage_components => false, 15 | manage_resources => false, 16 | node_on_server => false, 17 | } 18 | PUPPET 19 | end 20 | 21 | on_supported_os.each do |os, os_facts| 22 | context "on #{os}" do 23 | let(:facts) { os_facts } 24 | 25 | it { is_expected.to compile } 26 | it { is_expected.to contain_kubeconfig('/srv/kubernetes/kube-scheduler.kubeconf') } 27 | it { is_expected.not_to contain_file('/etc/kubernetes/manifests/kube-scheduler.yaml') } 28 | 29 | it do 30 | sysconf = '/etc/sysconfig' 31 | sysconf = '/etc/default' if os_facts['os']['family'] == 'Debian' 32 | 33 | is_expected.to contain_file(File.join(sysconf, 'kube-scheduler')). 34 | with_content( 35 | <<~SYSCONF 36 | ### NB: File managed by Puppet. 37 | ### Any changes will be overwritten. 38 | # 39 | ## Kubernetes Scheduler configuration 40 | # 41 | 42 | KUBE_SCHEDULER_ARGS="--leader-elect=true --kubeconfig=/srv/kubernetes/kube-scheduler.kubeconf" 43 | SYSCONF 44 | ).that_notifies('Service[kube-scheduler]') 45 | end 46 | 47 | it { is_expected.to contain_systemd__unit_file('kube-scheduler.service').that_notifies('Service[kube-scheduler]') } 48 | 49 | it do 50 | is_expected.to contain_service('kube-scheduler').with( 51 | ensure: 'running', 52 | enable: true 53 | ) 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppet-k8s", 3 | "version": "3.0.1-rc0", 4 | "author": "Vox Pupuli", 5 | "summary": "Install and manage plain Kubernetes installs", 6 | "license": "Apache-2.0", 7 | "source": "https://github.com/voxpupuli/puppet-k8s", 8 | "project_page": "https://github.com/voxpupuli/puppet-k8s", 9 | "issues_url": "https://github.com/voxpupuli/puppet-k8s/issues", 10 | "dependencies": [ 11 | { 12 | "name": "puppet-archive", 13 | "version_requirement": ">= 4.0.0 < 9.0.0" 14 | }, 15 | { 16 | "name": "puppetlabs-stdlib", 17 | "version_requirement": ">= 9.0.0 < 10.0.0" 18 | }, 19 | { 20 | "name": "puppet-kmod", 21 | "version_requirement": ">= 3.2.0 < 5.0.0" 22 | }, 23 | { 24 | "name": "puppet-augeasproviders_sysctl", 25 | "version_requirement": ">= 3.0.0 < 4.0.0" 26 | }, 27 | { 28 | "name": "puppet-systemd", 29 | "version_requirement": ">= 2.0.0 < 9.0.0" 30 | }, 31 | { 32 | "name": "puppetlabs-firewall", 33 | "version_requirement": ">= 7.0.0 < 9.0.0" 34 | }, 35 | { 36 | "name": "puppet-firewalld", 37 | "version_requirement": ">= 4.5.0 < 6.0.0" 38 | } 39 | ], 40 | "operatingsystem_support": [ 41 | { 42 | "operatingsystem": "CentOS", 43 | "operatingsystemrelease": [ 44 | "9" 45 | ] 46 | }, 47 | { 48 | "operatingsystem": "Debian", 49 | "operatingsystemrelease": [ 50 | "12" 51 | ] 52 | }, 53 | { 54 | "operatingsystem": "Ubuntu", 55 | "operatingsystemrelease": [ 56 | "20.04", 57 | "22.04" 58 | ] 59 | }, 60 | { 61 | "operatingsystem": "openSUSE", 62 | "operatingsystemrelease": [ 63 | "15.6" 64 | ] 65 | } 66 | ], 67 | "requirements": [ 68 | { 69 | "name": "openvox", 70 | "version_requirement": ">= 8.19.0 < 9.0.0" 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /manifests/common.pp: -------------------------------------------------------------------------------- 1 | # @summary Sets up common Kubernetes components - users/groups/folders/etc 2 | # @api private 3 | class k8s::common { 4 | assert_private() 5 | 6 | group { $k8s::group: 7 | ensure => present, 8 | system => true, 9 | gid => $k8s::gid, 10 | } 11 | 12 | user { $k8s::user: 13 | ensure => present, 14 | comment => 'Kubernetes user', 15 | gid => $k8s::group, 16 | home => '/srv/kubernetes', 17 | managehome => false, 18 | shell => (fact('os.family') ? { 19 | 'Debian' => '/usr/sbin/nologin', 20 | default => '/sbin/nologin', 21 | }), 22 | system => true, 23 | uid => $k8s::uid, 24 | } 25 | 26 | file { 27 | default: 28 | ensure => directory, 29 | force => true, 30 | purge => true, 31 | recurse => true; 32 | 33 | '/opt/k8s': ; 34 | '/opt/k8s/bin': ; 35 | } 36 | if $k8s::manage_facter { 37 | ['/etc/facter','/etc/facter/facts.d'].each |$path| { 38 | ensure_resource('file', $path, { ensure => directory }) 39 | } 40 | } 41 | 42 | file { '/var/run/kubernetes': 43 | ensure => directory, 44 | owner => $k8s::user, 45 | group => $k8s::group, 46 | } 47 | 48 | file { "${k8s::sysconfig_path}/kube-common": 49 | ensure => file, 50 | content => epp('k8s/sysconfig.epp', { 51 | comment => 'General Kubernetes Configuration', 52 | environment_variables => { 53 | 'KUBE_LOG_LEVEL' => '', 54 | }, 55 | }), 56 | } 57 | 58 | file { 59 | default: 60 | ensure => directory; 61 | 62 | '/etc/kubernetes': ; 63 | '/etc/kubernetes/certs': ; 64 | '/etc/kubernetes/manifests': 65 | purge => $k8s::purge_manifests, 66 | recurse => true; 67 | '/root/.kube': ; 68 | '/srv/kubernetes': 69 | owner => $k8s::user, 70 | group => $k8s::group; 71 | '/usr/libexec/kubernetes': ; 72 | '/var/lib/kubelet': ; 73 | '/var/lib/kubelet/pki': ; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/puppet/util/k8s.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Puppet::Util 4 | # Utility methods for K8s 5 | module K8s 6 | def self.content_diff(local, content) 7 | delete_merge = proc do |hash1, hash2| 8 | hash2.each_pair do |key, value| 9 | if hash1[key] != hash1[key.to_s] 10 | hash1[key.to_s] = hash1.delete key 11 | key = key.to_s 12 | end 13 | 14 | target_value = hash1[key] 15 | next if hash1.key?(key) && target_value.nil? 16 | 17 | if target_value.is_a?(Hash) && value.is_a?(Hash) && value.any? && target_value.any? 18 | delete_merge.call(target_value, value) 19 | elsif value.is_a?(Array) && target_value.is_a?(Array) && value.any? && target_value.any? 20 | diff = value.size != target_value.size 21 | target_value.each do |v| 22 | break if diff 23 | next if value.include? v 24 | 25 | if v.is_a? Hash 26 | diff ||= value.select { |ov| ov.is_a? Hash }. 27 | none? do |ov| 28 | v_copy = Marshal.load(Marshal.dump(v)) 29 | delete_merge.call(v_copy, ov) 30 | 31 | v_copy.empty? 32 | end 33 | else 34 | diff = true 35 | end 36 | end 37 | 38 | hash1.delete(key) unless diff 39 | elsif hash1.key?(key) && target_value == value 40 | hash1.delete(key) 41 | end 42 | 43 | hash1.delete(key) if hash1.key?(key) && (hash1[key].nil? || (hash1[key].respond_to?(:empty?) && hash1[key].empty?)) 44 | end 45 | hash1.dup.each_pair do |key, value| 46 | hash1.delete key if value.nil? && !hash2.key?(key) 47 | end 48 | 49 | hash1 50 | end 51 | 52 | # Verify that the intersection of upstream content and user-provided content is identical 53 | # This allows the upstream object to contain additional keys - such as those auto-generated by Kubernetes 54 | diff = Marshal.load(Marshal.dump(local)) 55 | delete_merge.call(diff, content) 56 | 57 | diff 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Puppet module for deploying k8s/kubernetes 2 | 3 | [![CI](https://github.com/voxpupuli/puppet-k8s/actions/workflows/ci.yml/badge.svg)](https://github.com/voxpupuli/puppet-k8s/actions/workflows/ci.yml) 4 | [![Puppet Forge](https://img.shields.io/puppetforge/v/puppet/k8s.svg)](https://forge.puppet.com/puppet/k8s) 5 | [![Puppet Forge - downloads](https://img.shields.io/puppetforge/dt/puppet/k8s.svg)](https://forge.puppet.com/puppet/k8s) 6 | [![Puppet Forge - endorsement](https://img.shields.io/puppetforge/e/puppet/k8s.svg)](https://forge.puppet.com/puppet/k8s) 7 | [![Puppet Forge - scores](https://img.shields.io/puppetforge/f/puppet/k8s.svg)](https://forge.puppet.com/puppet/k8s) 8 | [![License](https://img.shields.io/github/license/voxpupuli/puppet-k8s.svg)](https://github.com/voxpupuli/puppet-k8s/blob/master/LICENSE) 9 | 10 | ## Table of Contents 11 | 12 | - [Description](#description) 13 | - [Usage](#usage) 14 | - [Examples](#examples) 15 | - [Reference](#reference) 16 | 17 | ## Description 18 | 19 | This module installs, configures, and manages a Kubernetes cluster built from 20 | loose components. 21 | 22 | The main focus is towards the current stable versions of K8s (1.18.x+), but it 23 | should be able to handle both older and newer versions without issues. 24 | 25 | ## Usage 26 | 27 | Set k8s::server::etcd_servers to a list of servers or k8s::puppetdb_discovery to `true`. 28 | 29 | Setting up a server node (apiserver, controller-manager, scheduler): 30 | 31 | ```puppet 32 | class { 'k8s': 33 | role => 'server', 34 | control_plane_url => 'https://kubernetes.example.com:6443', 35 | # generate_ca => true, # Only set true temporarily to avoid overwriting the old secrets 36 | # puppetdb_discovery => true, # Will use PuppetDB PQL queries to manage etcd and nodes 37 | } 38 | ``` 39 | 40 | Setting up a client node (kubelet, kube-proxy): 41 | 42 | ```puppet 43 | class { 'k8s': 44 | role => 'node', 45 | control_plane_url => 'https://kubernetes.example.com:6443', 46 | } 47 | ``` 48 | 49 | ### Examples 50 | 51 | For more in-detail examples see the examples. 52 | 53 | - [Simple bridged setup](examples/simple_setup/Readme.md) 54 | - [Cilium setup](examples/cilium/Readme.md) 55 | 56 | ## Reference 57 | 58 | All parameters are documented within the classes. Markdown documentation is available in the [REFERENCE.md](REFERENCE.md) file, it also contains examples. 59 | -------------------------------------------------------------------------------- /lib/puppet/provider/kubeconfig/kubectl.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Uses kubectl to handle kubeconfig entries 4 | Puppet::Type.type(:kubeconfig).provide(:kubectl, parent: :ruby) do 5 | commands kubectl: 'kubectl' 6 | 7 | def update_cluster 8 | params = [] 9 | params << "--embed-certs=#{resource[:embed_certs]}" if resource[:embed_certs] && resource[:ca_cert] 10 | params << "--server=#{resource[:server]}" if resource[:server] 11 | params << "--certificate-authority=#{resource[:ca_cert]}" if resource[:ca_cert] 12 | params << "--insecure-skip-tls-verify=#{resource[:skip_tls_verify]}" if resource[:skip_tls_verify] 13 | params << "--tls-server-name=#{resource[:tls_server_name]}" if resource[:tls_server_name] 14 | 15 | kubectl_call( 16 | 'config', 'set-cluster', 17 | resource[:cluster], 18 | *params 19 | ) 20 | end 21 | 22 | def update_context 23 | params = [] 24 | params << "--cluster=#{resource[:cluster]}" 25 | params << "--user=#{resource[:user]}" 26 | params << "--namespace=#{resource[:namespace]}" 27 | 28 | kubectl_call( 29 | 'config', 'set-context', 30 | resource[:context], 31 | *params 32 | ) 33 | end 34 | 35 | def update_credentials 36 | params = [] 37 | embed = resource[:embed_certs] && (resource[:client_cert] || resource[:client_key]) 38 | params << "--embed-certs=#{resource[:embed_certs]}" if embed 39 | params << "--client-certificate=#{resource[:client_cert]}" if resource[:client_cert] 40 | params << "--client-key=#{resource[:client_key]}" if resource[:client_key] 41 | params << "--token=#{resource[:token]}" if resource[:token] 42 | params << "--token=#{File.read(resource[:token_file]).strip}" if resource[:token_file] 43 | params << "--username=#{resource[:username]}" if resource[:username] 44 | params << "--password=#{resource[:password]}" if resource[:password] 45 | 46 | kubectl_call( 47 | 'config', 'set-credentials', 48 | resource[:context], 49 | *params 50 | ) 51 | end 52 | 53 | def update_current_context 54 | kubectl_call( 55 | 'config', 'use-context', 56 | resource[:current_context] 57 | ) 58 | end 59 | 60 | def save; end 61 | 62 | def kubectl_call(*args) 63 | params = [ 64 | '--kubeconfig', 65 | resource[:path], 66 | ] 67 | kubectl(*params, *args) 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /manifests/server/tls/ca.pp: -------------------------------------------------------------------------------- 1 | # @summary Generates a TLS CA 2 | # 3 | # @param key The path to the CA key 4 | # @param cert The path to the CA certificate 5 | # @param ensure Whether the CA should be present or absent 6 | # @param subject The subject of the CA certificate 7 | # @param owner The owner of the CA key and certificate 8 | # @param group The group of the CA key and certificate 9 | # @param key_bits The number of bits in the CA key 10 | # @param valid_days The number of days the CA certificate is valid 11 | # @param generate Whether to generate the CA key and certificate 12 | # 13 | define k8s::server::tls::ca ( 14 | Stdlib::Unixpath $key, 15 | Stdlib::Unixpath $cert, 16 | K8s::Ensure $ensure = present, 17 | 18 | String[1] $subject = "/CN=${title}", 19 | 20 | String[1] $owner = 'root', 21 | String[1] $group = 'root', 22 | 23 | Integer[512] $key_bits = 2048, 24 | Integer[1] $valid_days = 10000, 25 | Boolean $generate = true, 26 | ) { 27 | if $ensure == 'present' { 28 | if $generate { 29 | Package <| title == 'openssl' |> 30 | -> exec { "Create ${title} CA key": 31 | command => "openssl genrsa -out '${key}' ${key_bits}", 32 | unless => "openssl pkey -in '${key}' -text | grep '${key_bits} bit'", 33 | path => $facts['path'], 34 | before => File[$key], 35 | } 36 | } 37 | 38 | Package <| title == 'openssl' |> 39 | -> exec { "Create ${title} CA cert": 40 | command => "openssl req -x509 -new -nodes -key '${key}' \ 41 | -days '${valid_days}' -out '${cert}' -subj '${subject}'", 42 | unless => "openssl x509 -CA '${cert}' -CAkey '${key}' -in '${cert}' -noout -set_serial 00", 43 | path => $facts['path'], 44 | subscribe => File[$key], 45 | before => File[$cert], 46 | } 47 | 48 | # Add a subscription if CA key is generated 49 | Exec <| title == "Create ${title} CA key" |> ~> Exec["Create ${title} CA cert"] 50 | } 51 | 52 | if !defined(File[$key]) { 53 | file { $key: 54 | ensure => $ensure, 55 | owner => $owner, 56 | group => $group, 57 | mode => '0600', 58 | replace => false, 59 | } 60 | } 61 | if !defined(File[$cert]) { 62 | file { $cert: 63 | ensure => $ensure, 64 | owner => $owner, 65 | group => $group, 66 | mode => '0644', 67 | replace => false, 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /spec/fixtures/files/resources/kube-proxy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | metadata: 3 | labels: 4 | k8s-app: kube-proxy 5 | kubernetes.io/managed-by: puppet 6 | spec: 7 | selector: 8 | matchLabels: 9 | k8s-app: kube-proxy 10 | updateStrategy: 11 | type: RollingUpdate 12 | template: 13 | metadata: 14 | labels: 15 | k8s-app: kube-proxy 16 | kubernetes.io/managed-by: puppet 17 | spec: 18 | priorityClassName: system-node-critical 19 | containers: 20 | - name: kube-proxy 21 | image: registry.k8s.io/kube-proxy:v1.23.4 22 | imagePullPolicy: IfNotPresent 23 | command: 24 | - "/go-runner" 25 | - "--log-file=/var/log/kube-proxy.log" 26 | - "--also-stdout=true" 27 | - "--" 28 | - "/usr/local/bin/kube-proxy" 29 | - "--hostname-override=$(NODE_NAME)" 30 | - "--config=/var/lib/kube-proxy/kube-proxy.conf" 31 | securityContext: 32 | privileged: true 33 | volumeMounts: 34 | - mountPath: "/var/lib/kube-proxy/kube-proxy.conf" 35 | name: kube-proxy 36 | subPath: kube-proxy.conf 37 | readOnly: true 38 | - mountPath: "/var/lib/kube-proxy/kubeconfig" 39 | name: kubeconfig 40 | subPath: kubeconfig 41 | readOnly: true 42 | - mountPath: "/run/xtables.lock" 43 | name: iptables-lock 44 | readOnly: null 45 | - mountPath: "/lib/modules" 46 | name: lib-modules 47 | readOnly: true 48 | env: 49 | - name: NODE_NAME 50 | valueFrom: 51 | fieldRef: 52 | fieldPath: spec.nodeName 53 | imagePullSecrets: null 54 | hostNetwork: true 55 | serviceAccountName: kube-proxy 56 | volumes: 57 | - name: logfile 58 | hostPath: 59 | path: "/var/log/kube-proxy.log" 60 | type: FileOrCreate 61 | - name: lib-modules 62 | hostPath: 63 | path: "/lib/modules" 64 | type: Directory 65 | - name: iptables-lock 66 | hostPath: 67 | path: "/run/xtables.lock" 68 | type: FileOrCreate 69 | - name: kube-proxy 70 | configMap: 71 | name: kube-proxy 72 | - name: kubeconfig 73 | configMap: 74 | name: kubeconfig-in-cluster 75 | tolerations: 76 | - operator: Exists 77 | nodeSelector: 78 | kubernetes.io/os: linux 79 | -------------------------------------------------------------------------------- /spec/classes/server/resources/boostrap_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::resources::bootstrap' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | function assert_private() {} 9 | 10 | include ::k8s 11 | class { '::k8s::server': 12 | manage_etcd => true, 13 | manage_certs => true, 14 | manage_components => false, 15 | manage_resources => false, 16 | node_on_server => false, 17 | } 18 | class { 'k8s::server::resources': 19 | manage_bootstrap => false, 20 | } 21 | PUPPET 22 | end 23 | 24 | on_supported_os.each do |os, os_facts| 25 | context "on #{os}" do 26 | let(:facts) { os_facts } 27 | 28 | it { is_expected.to compile } 29 | 30 | it { is_expected.to contain_k8s__server__bootstrap_token('puppet') } 31 | it { is_expected.not_to contain_kubectl_apply('cluster-info') } 32 | 33 | it { is_expected.to contain_kubectl_apply('puppet:cluster-info:reader Role') } 34 | it { is_expected.to contain_kubectl_apply('system:certificates.k8s.io:certificatesigningrequests:nodeclient') } 35 | it { is_expected.to contain_kubectl_apply('system:certificates.k8s.io:certificatesigningrequests:selfnodeclient') } 36 | it { is_expected.to contain_kubectl_apply('system:certificates.k8s.io:certificatesigningrequests:selfnodeserver') } 37 | 38 | it { is_expected.to contain_kubectl_apply('puppet:cluster-info:reader RoleBinding') } 39 | it { is_expected.to contain_kubectl_apply('system-bootstrap-node-bootstrapper') } 40 | it { is_expected.to contain_kubectl_apply('system-bootstrap-approve-node-client-csr') } 41 | it { is_expected.to contain_kubectl_apply('system-bootstrap-node-renewal') } 42 | it { is_expected.to contain_kubectl_apply('system-bootstrap-node-server-renewal') } 43 | 44 | describe 'with local CA files' do 45 | let(:facts) { super().merge(k8s_ca: Base64.strict_encode64('This is actually a CA PEM')) } 46 | 47 | it { is_expected.to contain_kubectl_apply('cluster-info') } 48 | end 49 | 50 | describe 'with specified secret' do 51 | let(:params) { { secret: sensitive('0123456789abcdef') } } 52 | 53 | it do 54 | is_expected.to contain_k8s__server__bootstrap_token('puppet'). 55 | with_secret(sensitive('0123456789abcdef')). 56 | with_update(true) 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/classes/server/etcd/setup_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::etcd::setup' do 6 | let(:params) do 7 | { 8 | version: '3.6.0' 9 | } 10 | end 11 | let(:pre_condition) do 12 | <<~PUPPET 13 | include ::k8s 14 | class { '::k8s::server': 15 | manage_etcd => false, 16 | manage_certs => false, 17 | manage_components => false, 18 | manage_resources => false, 19 | node_on_server => false, 20 | } 21 | class { '::k8s::server::etcd': 22 | manage_setup => false, 23 | } 24 | PUPPET 25 | end 26 | 27 | on_supported_os.each do |os, os_facts| 28 | context "on #{os}" do 29 | let(:facts) { os_facts } 30 | 31 | it { is_expected.to compile } 32 | 33 | it do 34 | is_expected.to contain_archive('etcd').with( 35 | ensure: 'present', 36 | path: '/opt/k8s/archives/etcd-v3.6.0-linux-amd64.tar.gz', 37 | source: 'https://storage.googleapis.com/etcd/v3.6.0/etcd-v3.6.0-linux-amd64.tar.gz', 38 | extract: true, 39 | extract_command: 'tar xfz %s --strip-components=1', 40 | extract_path: '/opt/k8s/etcd-3.6.0', 41 | cleanup: true, 42 | creates: ['/opt/k8s/etcd-3.6.0/etcd', '/opt/k8s/etcd-3.6.0/etcdctl'] 43 | ) 44 | 45 | is_expected.to contain_file('/usr/local/bin/etcd').with( 46 | ensure: 'link', 47 | mode: '0755', 48 | replace: true, 49 | target: '/opt/k8s/etcd-3.6.0/etcd' 50 | ).that_notifies('Service[etcd]') 51 | 52 | is_expected.to contain_file('/usr/local/bin/etcdctl').with( 53 | ensure: 'link', 54 | mode: '0755', 55 | replace: true, 56 | target: '/opt/k8s/etcd-3.6.0/etcdctl' 57 | ) 58 | end 59 | 60 | it { is_expected.to contain_user('etcd') } 61 | it { is_expected.to contain_group('etcd') } 62 | it { is_expected.to contain_file('/etc/etcd').with_ensure('directory') } 63 | it { is_expected.to contain_file('/var/lib/etcd').with_ensure('directory') } 64 | it { is_expected.to contain_file('/etc/etcd/etcd.conf') } 65 | it { is_expected.to contain_file('/etc/etcd/cluster.conf') } 66 | it { is_expected.to contain_systemd__unit_file('etcd.service') } 67 | it { is_expected.to contain_service('etcd').with_ensure('running').that_subscribes_to('File[/etc/etcd/etcd.conf]') } 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /manifests/install/crictl.pp: -------------------------------------------------------------------------------- 1 | # Class: k8s::install::crictl 2 | # 3 | # @summary installs the crictl debugging tool 4 | # 5 | # @param ensure set ensure for installation or deinstallation 6 | # @param version the k8s version 7 | # @param config config for crictl, for example: 8 | # k8s::install::crictl::config: 9 | # 'runtime-endpoint': 'unix:///run/containerd/containerd.sock' 10 | # 'image-endpoint': 'unix:///run/containerd/containerd.sock' 11 | # @param crictl_package the package name of crictl 12 | # @param manage_repo whether to manage the repo or not 13 | # @param download_url_template template string for the URL to download tar.gz from 14 | # 15 | class k8s::install::crictl ( 16 | Boolean $manage_repo = $k8s::manage_repo, 17 | Hash $config = {}, 18 | K8s::Ensure $ensure = $k8s::ensure, 19 | Optional[String[1]] $crictl_package = $k8s::crictl_package, 20 | String[1] $version = 'v1.26.0', 21 | Stdlib::HTTPUrl $download_url_template = 'https://github.com/kubernetes-sigs/cri-tools/releases/download/%{version}/crictl-%{version}-linux-%{arch}.tar.gz', 22 | ) { 23 | if $manage_repo { 24 | include k8s::repo 25 | 26 | $pkg = pick($crictl_package, 'cri-tools') 27 | package { $pkg: 28 | ensure => stdlib::ensure($ensure, 'package'), 29 | } 30 | 31 | Class['k8s::repo'] -> Package[$pkg] 32 | 33 | $config_require = Package[$pkg] 34 | } else { 35 | $_url = k8s::format_url($download_url_template, { 36 | version => $version, 37 | }) 38 | $_target = "/opt/k8s/crictl-${version}"; 39 | $_tarball_target = '/opt/k8s/archives'; 40 | 41 | file { $_target: 42 | ensure => stdlib::ensure($ensure, 'directory'), 43 | } 44 | 45 | archive { 'crictl': 46 | ensure => $ensure, 47 | path => "${_tarball_target}/crictl-${version}-linux.tar.gz", 48 | source => $_url, 49 | extract => true, 50 | extract_path => $_target, 51 | creates => "${_target}/crictl", 52 | cleanup => true, 53 | } 54 | 55 | file { '/usr/local/bin/crictl': 56 | ensure => stdlib::ensure($ensure, 'link'), 57 | mode => '0755', 58 | replace => true, 59 | target => "${_target}/crictl", 60 | require => Archive['crictl'], 61 | } 62 | 63 | $config_require = Archive['crictl'] 64 | } 65 | 66 | file { '/etc/crictl.yaml': 67 | ensure => $k8s::ensure, 68 | content => $config.to_yaml, 69 | require => $config_require, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/simple_setup/manifests/controller.pp: -------------------------------------------------------------------------------- 1 | # Class: profile::k8s::controller 2 | # 3 | # @param container_manager set the cri, like cri-o or containerd, if controller should be also a worker 4 | # @param etcd_version version of etcd 5 | # @param k8s_version version of kubernetes 6 | # @param manage_firewall whether to manage firewall or not 7 | # @param manage_kube_proxy whether to manage manage_kube_proxy or not 8 | # @param control_plane_url 9 | # api server url where the server/nodes connect to. 10 | # this is most likely a load balanced dns with all the controllers in the backend. 11 | # on single head clusters this may be the dns name:port of the controller node. 12 | # @param role role in the cluster, server, node, none 13 | # @param puppetdb_discovery whether to use puppetdb or not 14 | # @param service_cidr address space for the services 15 | # @param pod_cidr address space for the pods 16 | # 17 | # lint:ignore:autoloader_layout 18 | class profile::k8s::controller ( 19 | # lint:endignore 20 | Boolean $manage_firewall = true, # k8s-class default: false 21 | Boolean $manage_kube_proxy = true, # k8s-class default: true 22 | Boolean $puppetdb_discovery = true, # k8s-class default: false 23 | Stdlib::HTTPUrl $control_plane_url = 'https://kubernetes:6443', # k8s-class default: https://kubernetes:6443 24 | String[1] $etcd_version = '3.5.1', # k8s-class default: 3.5.1 25 | String[1] $k8s_version = '1.26.1', # k8s-class default: 1.26.1 26 | Enum['server'] $role = 'server', # k8s-class default: none 27 | K8s::CIDR $service_cidr = '10.20.0.0/20', # k8s-class default: 10.1.0.0/24 28 | K8s::CIDR $pod_cidr = '10.20.16.0/20', # k8s-class default: 10.0.0.0/16 29 | K8s::Container_runtimes $container_manager = $profile::k8s::node::container_manager, 30 | ) { 31 | class { 'k8s': 32 | container_manager => $container_manager, 33 | etcd_version => $etcd_version, 34 | manage_firewall => $manage_firewall, 35 | manage_kube_proxy => $manage_kube_proxy, 36 | control_plane_url => $control_plane_url, 37 | role => $role, 38 | version => $k8s_version, 39 | service_cluster_cidr => $service_cidr, 40 | cluster_cidr => $pod_cidr, 41 | puppetdb_discovery => $puppetdb_discovery, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /manifests/server/bootstrap_token.pp: -------------------------------------------------------------------------------- 1 | # @summary Generates and stores a kubelet bootstrap token into the cluster 2 | # 3 | # You generally only want this to be done on a single Kubernetes server 4 | # 5 | # @param addn_data Additional data to add to the token 6 | # @param description A description of the token 7 | # @param ensure Whether the token should be present or absent 8 | # @param expiration The expiration time of the token 9 | # @param extra_groups An array of extra groups to add to the token 10 | # @param id The ID of the token to generate 11 | # @param kubeconfig The path to the kubeconfig file to use 12 | # @param secret The secret to use for the token 13 | # @param update Whether to update the token if it already exists 14 | # @param use_authentication Whether the token should be used for authentication 15 | # @param use_signing Whether the token should be used for signing 16 | # 17 | define k8s::server::bootstrap_token ( 18 | Stdlib::Unixpath $kubeconfig, 19 | K8s::Ensure $ensure = 'present', 20 | 21 | Pattern[/^[a-z0-9]{6}$/] $id = $name, 22 | Sensitive[K8s::Bootstrap_token] $secret = Sensitive(fqdn_rand_string(16).downcase()), 23 | Boolean $use_authentication = true, 24 | Boolean $update = false, 25 | 26 | Optional[String] $description = undef, 27 | Optional[K8s::Timestamp] $expiration = undef, 28 | Optional[Boolean] $use_signing = undef, 29 | Optional[Array[String]] $extra_groups = undef, 30 | 31 | Hash[String,Data] $addn_data = {} 32 | ) { 33 | $_extra_groups = pick($extra_groups, []).join(',') 34 | $_secret_data = Hash({ 35 | 'token-id' => $id, 36 | 'token-secret' => $secret.unwrap, 37 | 'description' => $description, 38 | 'expiration' => $expiration, 39 | 'usage-bootstrap-authentication' => $use_authentication, 40 | 'usage-bootstrap-signing' => $use_signing, 41 | 'auth-extra-groups' => $_extra_groups, 42 | }.filter |$k, $v| { 43 | $v != undef and $v != '' 44 | }.map |$k, $v| { 45 | [$k, String(Binary.new(String($v), '%s'))] 46 | }) 47 | 48 | kubectl_apply { "bootstrap-token-${id}": 49 | ensure => $ensure, 50 | provider => 'kubectl', 51 | kubeconfig => $kubeconfig, 52 | namespace => 'kube-system', 53 | update => $update, 54 | 55 | api_version => 'v1', 56 | kind => 'Secret', 57 | 58 | content => { 59 | 'type' => 'bootstrap.kubernetes.io/token', 60 | 'data' => $_secret_data, 61 | }.deep_merge($addn_data), 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/puppet/provider/kubectl_apply/kubectl.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path('../../util/k8s', __dir__) 4 | require 'tempfile' 5 | 6 | # Applies resources as data in a Kubernetes cluster 7 | Puppet::Type.type(:kubectl_apply).provide(:kubectl) do 8 | commands kubectl: 'kubectl' 9 | defaultfor kernel: %r{.*} 10 | 11 | attr_reader :resource_diff, :exists_in_cluster 12 | 13 | def exists? 14 | data = kubectl_get 15 | return false unless exists_in_cluster 16 | 17 | diff = content_diff(data) 18 | return true if resource[:ensure].to_s == 'absent' || resource[:update] == false 19 | 20 | diff.empty? 21 | end 22 | 23 | def create 24 | tempfile = Tempfile.new('kubectl_apply') 25 | tempfile.write resource_hash.to_json 26 | tempfile.flush 27 | if resource[:recreate] && resource_diff && exists_in_cluster 28 | kubectl_cmd 'delete', '-f', tempfile.path, '--cascade=orphan', '--wait=false' 29 | kubectl_cmd 'create', '-f', tempfile.path 30 | elsif resource_diff && exists_in_cluster 31 | kubectl_cmd 'patch', '-f', tempfile.path, '-p', resource_diff.to_json, '--type', 'merge' 32 | elsif !exists_in_cluster 33 | kubectl_cmd 'create', '-f', tempfile.path 34 | end 35 | ensure 36 | tempfile.close! 37 | end 38 | 39 | def destroy 40 | kubectl_cmd 'delete', resource[:kind], resource[:resource_name] 41 | end 42 | 43 | def content_diff(content, store: true) 44 | diff = Puppet::Util::K8s.content_diff(resource[:content], content) 45 | 46 | @resource_diff = diff if store 47 | diff 48 | end 49 | 50 | def resource_hash 51 | hash = resource[:content] 52 | 53 | hash['apiVersion'] = resource[:api_version] 54 | hash['kind'] = resource[:kind] 55 | 56 | metadata = hash['metadata'] ||= {} 57 | metadata['name'] = resource[:resource_name] 58 | metadata['namespace'] = resource[:namespace] if resource[:namespace] 59 | 60 | hash 61 | end 62 | 63 | private 64 | 65 | def kubectl_get 66 | @data ||= kubectl_cmd 'get', resource[:kind], resource[:resource_name], '--output', 'json' 67 | @exists_in_cluster = true 68 | JSON.parse(@data) 69 | rescue Puppet::ExecutionFailure => e 70 | if %r{Error from server \(NotFound\)}.match?(e.message) 71 | @exists_in_cluster = false 72 | return {} 73 | end 74 | 75 | raise 76 | rescue StandardError => e 77 | raise Puppet::Error, "#{e.class}: #{e}" 78 | end 79 | 80 | def kubectl_cmd(*args) 81 | params = [] 82 | if resource[:namespace] 83 | params << '--namespace' 84 | params << resource[:namespace] 85 | end 86 | if resource[:kubeconfig] 87 | params << '--kubeconfig' 88 | params << resource[:kubeconfig] 89 | end 90 | 91 | kubectl(*params, *args) 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /spec/defines/binary_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::binary' do 6 | let(:title) { 'namevar' } 7 | let(:params) do 8 | { 9 | version: '1.0', 10 | } 11 | end 12 | 13 | let(:pre_condition) do 14 | 'include k8s' 15 | end 16 | 17 | on_supported_os.each do |os, os_facts| 18 | context "on #{os}" do 19 | let(:facts) { os_facts } 20 | 21 | it { is_expected.to compile } 22 | 23 | it { is_expected.to contain_file('/opt/k8s/1.0').with(ensure: 'directory') } 24 | 25 | %w[package tarball loose hyperkube].each do |method| 26 | context "using #{method} packaging" do 27 | %w[kubelet kube-apiserver kubectl].each do |binary| 28 | context "for binary #{binary}" do 29 | let(:title) { binary } 30 | let(:params) do 31 | { 32 | ensure: 'present', 33 | version: '1.0', 34 | packaging: method, 35 | } 36 | end 37 | 38 | it { is_expected.to compile } 39 | 40 | it do 41 | is_expected.to contain_file("/opt/k8s/1.0/#{binary}").with( 42 | ensure: 'present', 43 | mode: '0755' 44 | ) 45 | end 46 | 47 | if method == 'package' 48 | it do 49 | is_expected.to contain_file("/usr/bin/#{binary}").with( 50 | ensure: 'present', 51 | mode: '0755' 52 | ) 53 | end 54 | else 55 | it do 56 | is_expected.to contain_file("/usr/bin/#{binary}").with( 57 | ensure: 'link', 58 | mode: '0755', 59 | replace: true, 60 | target: "/opt/k8s/1.0/#{binary}" 61 | ) 62 | end 63 | end 64 | 65 | case method 66 | when 'loose' 67 | it do 68 | is_expected.to contain_file("/opt/k8s/1.0/#{binary}").with( 69 | ensure: 'present', 70 | mode: '0755', 71 | source: "https://dl.k8s.io/release/v1.0/bin/linux/amd64/#{binary}" 72 | ) 73 | end 74 | when 'hyperkube' 75 | it do 76 | is_expected.to contain_file('/opt/k8s/1.0/hyperkube').with( 77 | ensure: 'present', 78 | mode: '0755', 79 | source: 'https://dl.k8s.io/release/v1.0/bin/linux/amd64/hyperkube' 80 | ) 81 | end 82 | end 83 | end 84 | end 85 | end 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/classes/node/kube_proxy_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::node::kube_proxy' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | function assert_private() {} 9 | 10 | include ::k8s 11 | class { '::k8s::node': 12 | manage_kubelet => false, 13 | manage_proxy => false, 14 | } 15 | PUPPET 16 | end 17 | 18 | on_supported_os.each do |os, os_facts| 19 | context "on #{os}" do 20 | let(:facts) { os_facts } 21 | 22 | it { is_expected.to compile } 23 | 24 | it do 25 | sysconf = '/etc/sysconfig' 26 | sysconf = '/etc/default' if os_facts['os']['family'] == 'Debian' 27 | 28 | is_expected.to contain_file(File.join(sysconf, 'kube-proxy')). 29 | that_notifies('Service[kube-proxy]') 30 | end 31 | 32 | it { is_expected.to contain_systemd__unit_file('kube-proxy.service').that_notifies('Service[kube-proxy]') } 33 | it { is_expected.to contain_service('kube-proxy') } 34 | 35 | context 'with cert auth' do 36 | let(:params) do 37 | { 38 | auth: 'cert', 39 | ca_cert: '/tmp/ca.pem', 40 | cert: '/tmp/cert.pem', 41 | key: '/tmp/key.pem', 42 | } 43 | end 44 | 45 | it do 46 | is_expected.to contain_kubeconfig('/srv/kubernetes/kube-proxy.kubeconf'). 47 | with_ca_cert('/tmp/ca.pem'). 48 | with_client_cert('/tmp/cert.pem'). 49 | with_client_key('/tmp/key.pem'). 50 | that_notifies('Service[kube-proxy]') 51 | end 52 | end 53 | 54 | context 'with token auth' do 55 | let(:params) do 56 | { 57 | auth: 'token', 58 | ca_cert: '/tmp/ca.pem', 59 | token: sensitive('blah'), 60 | } 61 | end 62 | 63 | it do 64 | is_expected.to contain_kubeconfig('/srv/kubernetes/kube-proxy.kubeconf'). 65 | with_ca_cert('/tmp/ca.pem'). 66 | with_token('blah'). 67 | that_notifies('Service[kube-proxy]') 68 | end 69 | end 70 | 71 | context 'with incluster auth' do 72 | let(:params) do 73 | { 74 | auth: 'incluster', 75 | } 76 | end 77 | 78 | it { is_expected.not_to contain_kubeconfig('/srv/kubernetes/kube-proxy.kubeconf') } 79 | it { is_expected.to contain_k8s__binary('kube-proxy').with_ensure('absent') } 80 | 81 | it do 82 | sysconf = '/etc/sysconfig' 83 | sysconf = '/etc/default' if os_facts['os']['family'] == 'Debian' 84 | 85 | is_expected.to contain_file(File.join(sysconf, 'kube-proxy')).with_ensure('absent') 86 | end 87 | 88 | it { is_expected.to contain_systemd__unit_file('kube-proxy.service').with_ensure('absent') } 89 | it { is_expected.to contain_service('kube-proxy').with_ensure('stopped').with_enable(false) } 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /templates/server/etcd/etcd.conf.epp: -------------------------------------------------------------------------------- 1 | <%- | 2 | String[1] $etcd_name, 3 | String[1] $data_dir, 4 | Optional[Stdlib::Unixpath] $wal_dir = undef, 5 | 6 | Enum['on','off','readonly'] $proxy, 7 | 8 | Array[Stdlib::HTTPUrl] $listen_client_urls, 9 | Array[Stdlib::HTTPUrl] $advertise_client_urls, 10 | Array[Stdlib::HTTPUrl] $listen_peer_urls, 11 | Array[Stdlib::HTTPUrl] $initial_advertise_peer_urls, 12 | 13 | Boolean $enable_v2 = false, 14 | 15 | Optional[Stdlib::Unixpath] $cert_file = undef, 16 | Optional[Stdlib::Unixpath] $key_file = undef, 17 | Optional[Stdlib::Unixpath] $trusted_ca_file = undef, 18 | Boolean $client_cert_auth = false, 19 | Boolean $auto_tls = false, 20 | 21 | Optional[Stdlib::Unixpath] $peer_cert_file = undef, 22 | Optional[Stdlib::Unixpath] $peer_key_file = undef, 23 | Optional[Stdlib::Unixpath] $peer_trusted_ca_file = undef, 24 | Boolean $peer_client_cert_auth = false, 25 | Boolean $peer_auto_tls = false, 26 | 27 | Optional[Integer] $auto_compaction_retention = undef, 28 | Optional[Enum['existing', 'new']] $initial_cluster_state = undef, 29 | Optional[String] $initial_cluster_token = undef, 30 | 31 | Boolean $debug = false, 32 | | -%> 33 | # Managed by Puppet 34 | 35 | #[member] 36 | ETCD_NAME="<%= $etcd_name %>" 37 | ETCD_DATA_DIR="<%= $data_dir %>" 38 | <%- if $wal_dir { -%> 39 | ETCD_WAL_DIR="<%= $wal_dir %>" 40 | <%- } else { -%> 41 | # ETCD_WAL_DIR="" # Use default 42 | <%- } -%> 43 | ETCD_LISTEN_PEER_URLS="<%= $listen_peer_urls.join(',') %>" 44 | ETCD_LISTEN_CLIENT_URLS="<%= $listen_client_urls.join(',') %>" 45 | 46 | #[cluster] 47 | ETCD_INITIAL_ADVERTISE_PEER_URLS="<%= $initial_advertise_peer_urls.join(',') %>" 48 | # ETCD_INITIAL_CLUSTER="..." # Configured in /etc/etcd/cluster.conf 49 | <% if $initial_cluster_state { -%> 50 | ETCD_INITIAL_CLUSTER_STATE="<%= $initial_cluster_state %>" 51 | <% } -%> 52 | <% if $initial_cluster_token { -%> 53 | ETCD_INITIAL_CLUSTER_TOKEN="<%= $initial_cluster_token %>" 54 | <% } -%> 55 | ETCD_ADVERTISE_CLIENT_URLS="<%= $advertise_client_urls.join(',') %>" 56 | <% if $auto_compaction_retention { -%> 57 | ETCD_AUTO_COMPACTION_RETENTION=<%= $auto_compaction_retention %> 58 | <% } -%> 59 | ETCD_ENABLE_V2=<%= $enable_v2 %> 60 | 61 | #[proxy] 62 | ETCD_PROXY="<%= $proxy %>" 63 | 64 | #[security] 65 | <% if $cert_file { -%> 66 | ETCD_CERT_FILE="<%= $cert_file %>" 67 | <% } -%> 68 | <% if $key_file { -%> 69 | ETCD_KEY_FILE="<%= $key_file %>" 70 | <% } -%> 71 | ETCD_CLIENT_CERT_AUTH=<%= $client_cert_auth %> 72 | <% if $trusted_ca_file { -%> 73 | ETCD_TRUSTED_CA_FILE="<%= $trusted_ca_file %>" 74 | <% } -%> 75 | <% if $auto_tls { -%> 76 | ETCD_AUTO_TLS="<%= $auto_tls %>" 77 | <% } -%> 78 | <% if $peer_cert_file { -%> 79 | ETCD_PEER_CERT_FILE="<%= $peer_cert_file %>" 80 | <% } -%> 81 | <% if $peer_key_file { -%> 82 | ETCD_PEER_KEY_FILE="<%= $peer_key_file %>" 83 | <% } -%> 84 | ETCD_PEER_CLIENT_CERT_AUTH=<%= $peer_client_cert_auth %> 85 | <% if $peer_trusted_ca_file { -%> 86 | ETCD_PEER_TRUSTED_CA_FILE="<%= $peer_trusted_ca_file %>" 87 | <% } -%> 88 | <% if $peer_auto_tls { -%> 89 | ETCD_PEER_AUTO_TLS="<%= $peer_auto_tls %>" 90 | <% } -%> 91 | 92 | #[logging] 93 | ETCD_DEBUG=<%= $debug %> 94 | -------------------------------------------------------------------------------- /spec/support/shared_behaviour.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'tmpdir' 5 | 6 | RSpec.shared_examples 'a kubeconfig provider' do |provider_class| 7 | describe provider_class do 8 | let(:tmpfile) do 9 | tmpfilename('kubeconfig_test') 10 | end 11 | let(:resource_properties) do 12 | { 13 | title: 'test', 14 | path: tmpfile, 15 | server: 'https://kubernetes.home.lan:6443', 16 | } 17 | end 18 | let(:resource) do 19 | Puppet::Type::Kubeconfig.new(resource_properties) 20 | end 21 | 22 | let(:provider) do 23 | provider_class.new(resource) 24 | end 25 | 26 | context 'with sample config file' do 27 | let(:sample_config) do 28 | { 29 | 'apiVersion' => 'v1', 30 | 'clusters' => [ 31 | { 32 | 'name' => 'default', 33 | 'cluster' => { 34 | 'server' => 'https://kubernetes.home.lan:6443', 35 | }, 36 | }, 37 | ], 38 | 'contexts' => [ 39 | { 40 | 'name' => 'default', 41 | 'context' => { 42 | 'cluster' => 'default', 43 | 'namespace' => 'default', 44 | 'user' => 'default', 45 | }, 46 | }, 47 | ], 48 | 'users' => [ 49 | { 50 | 'name' => 'default', 51 | 'user' => {}, 52 | }, 53 | ], 54 | 'current-context' => 'default', 55 | 'kind' => 'Config', 56 | 'preferences' => {}, 57 | } 58 | end 59 | 60 | before do 61 | File.write(tmpfile, Psych.dump(sample_config)) 62 | end 63 | 64 | it 'detects existing entries' do 65 | expect(provider.exists?).to eq true 66 | end 67 | 68 | it 'detects changes' do 69 | provider.kubeconfig_content['apiVersion'] = 'v2' 70 | expect(provider.changed?).to eq true 71 | end 72 | 73 | it 'detects faulty clusters' do 74 | provider.kubeconfig_content['clusters'][0]['cluster']['server'] = 'http://example.com' 75 | expect(provider.exists?).to eq false 76 | end 77 | 78 | it 'detects missing clusters' do 79 | provider.kubeconfig_content['clusters'].clear 80 | expect(provider.exists?).to eq false 81 | end 82 | 83 | it 'detects missing contexts' do 84 | provider.kubeconfig_content['contexts'].clear 85 | expect(provider.exists?).to eq false 86 | end 87 | 88 | it 'detects faulty contexts' do 89 | provider.kubeconfig_content['contexts'][0]['context']['cluster'] = 'example' 90 | expect(provider.exists?).to eq false 91 | end 92 | 93 | it 'detects missing users' do 94 | provider.kubeconfig_content['users'].clear 95 | expect(provider.exists?).to eq false 96 | end 97 | 98 | it 'detects faulty users' do 99 | provider.kubeconfig_content['users'][0]['user']['token'] = 'example' 100 | expect(provider.exists?).to eq false 101 | end 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /spec/classes/server/etcd_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::etcd' do 6 | let(:params) do 7 | { 8 | generate_ca: true, 9 | manage_certs: true, 10 | manage_members: true 11 | } 12 | end 13 | 14 | context "with k8s included in server mode" do 15 | let(:pre_condition) do 16 | <<~PUPPET 17 | function puppetdb_query(String[1] $data) { 18 | return [ 19 | { 20 | certname => 'node.example.com', 21 | parameters => { 22 | etcd_name => 'node', 23 | initial_advertise_peer_urls => ['https://node.example.com:2380'], 24 | } 25 | } 26 | ] 27 | } 28 | 29 | include ::k8s 30 | class { '::k8s::server': 31 | manage_etcd => false, 32 | manage_certs => false, 33 | manage_components => false, 34 | manage_resources => false, 35 | node_on_server => false, 36 | } 37 | PUPPET 38 | end 39 | 40 | on_supported_os.each do |os, os_facts| 41 | context "on #{os}" do 42 | let(:facts) { os_facts } 43 | 44 | it { is_expected.to compile } 45 | 46 | it do 47 | %w[etcd-peer-ca etcd-client-ca].each do |ca| 48 | is_expected.to contain_k8s__server__tls__ca(ca) 49 | end 50 | end 51 | 52 | it do 53 | %w[etcd-peer etcd-client].each do |cert| 54 | is_expected.to contain_k8s__server__tls__cert(cert) 55 | end 56 | end 57 | 58 | it { is_expected.to contain_class('k8s::server::etcd::setup') } 59 | it { is_expected.to contain_k8s__server__etcd__member('node').with_peer_urls(['https://node.example.com:2380']) } 60 | end 61 | end 62 | end 63 | 64 | context "with k8s included" do 65 | let(:pre_condition) do 66 | <<~PUPPET 67 | function puppetdb_query(String[1] $data) { 68 | return [ 69 | { 70 | certname => 'node.example.com', 71 | parameters => { 72 | etcd_name => 'node', 73 | initial_advertise_peer_urls => ['https://node.example.com:2380'], 74 | } 75 | } 76 | ] 77 | } 78 | 79 | include ::k8s 80 | PUPPET 81 | end 82 | 83 | on_supported_os.each do |os, os_facts| 84 | context "on #{os}" do 85 | let(:facts) { os_facts } 86 | 87 | it { is_expected.to compile } 88 | 89 | it do 90 | %w[etcd-peer-ca etcd-client-ca].each do |ca| 91 | is_expected.to contain_k8s__server__tls__ca(ca) 92 | end 93 | end 94 | 95 | it do 96 | %w[etcd-peer etcd-client].each do |cert| 97 | is_expected.to contain_k8s__server__tls__cert(cert) 98 | end 99 | end 100 | 101 | it { is_expected.to contain_class('k8s::server::etcd::setup') } 102 | it { is_expected.to contain_k8s__server__etcd__member('node').with_peer_urls(['https://node.example.com:2380']) } 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/defines/server/tls/cert_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::tls::cert' do 6 | let(:title) { 'namevar' } 7 | let(:params) do 8 | { 9 | cert_path: '/tmp/certs', 10 | ca_key: '/tmp/ca.key', 11 | ca_cert: '/tmp/ca.pem', 12 | 13 | distinguished_name: { 14 | commonName: 'test-cert' 15 | }, 16 | addn_names: [ 17 | '172.31.0.0', 18 | 'example.com', 19 | '2001::19', 20 | ], 21 | } 22 | end 23 | 24 | on_supported_os.each do |os, os_facts| 25 | context "on #{os}" do 26 | let(:facts) { os_facts } 27 | 28 | it { is_expected.to compile } 29 | 30 | it do 31 | is_expected.to contain_file('/tmp/certs/namevar.cnf').with( 32 | content: <<~CNF 33 | [req] 34 | distinguished_name = req_distinguished_name 35 | req_extensions = v3_req 36 | prompt = no 37 | 38 | [req_distinguished_name] 39 | commonName = test-cert 40 | 41 | [v3_req] 42 | basicConstraints = CA:FALSE 43 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 44 | extendedKeyUsage = clientAuth 45 | subjectAltName = @alt_names 46 | 47 | [alt_names] 48 | DNS.1 = example.com 49 | IP.1 = 172.31.0.0 50 | IP.2 = 2001::19 51 | CNF 52 | ) 53 | end 54 | 55 | it do 56 | is_expected.to contain_exec('Create K8s namevar key').with( 57 | command: "openssl genrsa -out '/tmp/certs/namevar.key' 2048; echo > '/tmp/certs/namevar.pem'", 58 | unless: "openssl pkey -in '/tmp/certs/namevar.key' -text | grep '2048 bit'" 59 | ).that_notifies('Exec[Create K8s namevar CSR]') 60 | end 61 | 62 | it do 63 | is_expected.to contain_exec('Create K8s namevar CSR').with( 64 | command: %r{openssl req -new -key '/tmp/certs/namevar\.key' \s+ -out '/tmp/certs/namevar\.csr' -config '/tmp/certs/namevar\.cnf'; echo > '/tmp/certs/namevar\.pem'}, 65 | refreshonly: true 66 | ).that_requires('File[/tmp/certs/namevar.key]') 67 | end 68 | 69 | it do 70 | is_expected.to contain_exec('Sign K8s namevar cert').with( 71 | command: %r{openssl x509 -req -in '/tmp/certs/namevar\.csr' \s+ -CA '/tmp/ca.pem' -CAkey '/tmp/ca\.key' -CAcreateserial \s+ -out \ 72 | '/tmp/certs/namevar\.pem' -days '10000' \s+ -extensions v3_req -extfile '/tmp/certs/namevar\.cnf'}, 73 | unless: "openssl verify -CAfile '/tmp/ca.pem' '/tmp/certs/namevar.pem'" 74 | ).that_requires(['File[/tmp/certs/namevar.key]', 'File[/tmp/certs/namevar.csr]']) 75 | end 76 | 77 | it do 78 | is_expected.to contain_file('/tmp/certs/namevar.key').with( 79 | ensure: 'present', 80 | owner: 'root', 81 | group: 'root', 82 | mode: '0600', 83 | replace: false 84 | ) 85 | end 86 | 87 | it do 88 | is_expected.to contain_file('/tmp/certs/namevar.pem').with( 89 | ensure: 'present', 90 | owner: 'root', 91 | group: 'root', 92 | mode: '0640', 93 | replace: false 94 | ) 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/kubeconfig/ruby_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | ruby_provider = Puppet::Type.type(:kubeconfig).provider(:ruby) 6 | 7 | RSpec.describe ruby_provider do 8 | it_behaves_like 'a kubeconfig provider', ruby_provider 9 | 10 | describe 'ruby provider' do 11 | let(:tmpfile) do 12 | tmpfilename('kubeconfig_test') 13 | end 14 | 15 | let(:name) { tmpfile } 16 | let(:resource_properties) do 17 | { 18 | name: name, 19 | server: 'https://kubernetes.home.lan:6443', 20 | } 21 | end 22 | let(:default_kubeconfig) do 23 | { 24 | 'apiVersion' => 'v1', 25 | 'clusters' => [ 26 | { 27 | 'name' => 'default', 28 | 'cluster' => { 29 | 'server' => 'https://kubernetes.home.lan:6443', 30 | }, 31 | }, 32 | ], 33 | 'contexts' => [ 34 | { 35 | 'name' => 'default', 36 | 'context' => { 37 | 'cluster' => 'default', 38 | 'namespace' => 'default', 39 | 'user' => 'default', 40 | }, 41 | }, 42 | ], 43 | 'users' => [ 44 | { 45 | 'name' => 'default', 46 | 'user' => {}, 47 | }, 48 | ], 49 | 'current-context' => 'default', 50 | 'kind' => 'Config', 51 | 'preferences' => {}, 52 | } 53 | end 54 | let(:resource) { Puppet::Type::Kubeconfig.new(resource_properties) } 55 | let(:provider) { ruby_provider.new(resource) } 56 | 57 | before do 58 | resource.provider = provider 59 | end 60 | 61 | context 'no extra properties specified' do 62 | it 'creates default config, updates components' do 63 | provider.create 64 | 65 | stat = File.stat(tmpfile) 66 | expect(stat.mode & 0o777).to eq 0o600 67 | 68 | expect(Psych.load(File.read(tmpfile))).to eq default_kubeconfig 69 | end 70 | end 71 | 72 | context 'ca certificate provided' do 73 | let :catmpfile do 74 | tmpfilename('kubeconfig_ca.crt_test') 75 | end 76 | let(:resource_properties) do 77 | { 78 | name: name, 79 | server: 'https://kubernetes.home.lan:6443', 80 | ca_cert: catmpfile, 81 | } 82 | end 83 | let(:resulting_kubeconfig) do 84 | conf = default_kubeconfig.dup 85 | conf['clusters'][0]['cluster']['certificate-authority-data'] = 'Y2EuY3J0' 86 | conf 87 | end 88 | 89 | before do 90 | File.write(catmpfile, 'ca.crt') 91 | end 92 | 93 | it 'creates default config, updates components' do 94 | provider.create 95 | 96 | expect(Psych.load(File.read(tmpfile))).to eq resulting_kubeconfig 97 | end 98 | end 99 | 100 | context 'when applied' do 101 | it 'applies correctly' do 102 | allow(Puppet::Util::Storage).to receive(:store) 103 | 104 | catalog = Puppet::Resource::Catalog.new 105 | catalog.add_resource(resource) 106 | catalog.apply 107 | 108 | expect(Psych.load(File.read(tmpfile))).to eq default_kubeconfig 109 | end 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /manifests/node.pp: -------------------------------------------------------------------------------- 1 | # @summary Installs a Kubernetes node 2 | # 3 | # @param ca_cert path to the ca cert 4 | # @param cert_path path to cert files 5 | # @param control_plane_url cluster API connection 6 | # @param ensure set ensure for installation or deinstallation 7 | # @param firewall_type define the type of firewall to use 8 | # @param manage_crictl toggle to install crictl 9 | # @param manage_firewall whether to manage firewall or not 10 | # @param manage_packages whether to manage packages 11 | # @param manage_kernel_modules whether to load kernel modules or not 12 | # @param manage_kubelet whether to manage kublet or not 13 | # @param manage_proxy whether to manage kube-proxy or not 14 | # @param manage_simple_cni toggle to use a simple bridge network for containers 15 | # @param manage_sysctl_settings whether to manage sysctl settings or not 16 | # @param node_auth type of node authentication 17 | # @param node_cert path to node cert file 18 | # @param node_key path to node key file 19 | # @param node_token k8s token to join a cluster 20 | # @param proxy_auth which proxy auth to use 21 | # @param proxy_cert path to proxy cert file 22 | # @param proxy_key path to proxy key file 23 | # @param proxy_token k8s token for kube-proxy 24 | # @param puppetdb_discovery_tag enable puppetdb resource searching 25 | # 26 | class k8s::node ( 27 | K8s::Ensure $ensure = $k8s::ensure, 28 | 29 | Stdlib::HTTPUrl $control_plane_url = $k8s::control_plane_url, 30 | K8s::Node_auth $node_auth = $k8s::node_auth, 31 | K8s::Proxy_auth $proxy_auth = 'incluster', 32 | 33 | Boolean $manage_kubelet = true, 34 | Boolean $manage_proxy = $k8s::manage_kube_proxy == 'on-node', 35 | Boolean $manage_crictl = false, 36 | Boolean $manage_firewall = $k8s::manage_firewall, 37 | Boolean $manage_packages = $k8s::manage_packages, 38 | Boolean $manage_kernel_modules = $k8s::manage_kernel_modules, 39 | Boolean $manage_sysctl_settings = $k8s::manage_sysctl_settings, 40 | Boolean $manage_simple_cni = false, 41 | String[1] $puppetdb_discovery_tag = $k8s::puppetdb_discovery_tag, 42 | 43 | Stdlib::Unixpath $cert_path = '/var/lib/kubelet/pki', 44 | Stdlib::Unixpath $ca_cert = "${cert_path}/ca.pem", 45 | 46 | # For cert auth 47 | Optional[Stdlib::Unixpath] $node_cert = undef, 48 | Optional[Stdlib::Unixpath] $node_key = undef, 49 | 50 | Optional[Stdlib::Unixpath] $proxy_cert = undef, 51 | Optional[Stdlib::Unixpath] $proxy_key = undef, 52 | 53 | # For token and bootstrap auth 54 | Optional[Sensitive[String]] $node_token = undef, 55 | Optional[Sensitive[String]] $proxy_token = undef, 56 | 57 | Optional[K8s::Firewall] $firewall_type = $k8s::firewall_type, 58 | ) { 59 | include k8s::common 60 | include k8s::install::cni_plugins 61 | 62 | if $k8s::manage_container_manager { 63 | include k8s::install::container_runtime 64 | } 65 | if $k8s::manage_repo { 66 | include k8s::repo 67 | } 68 | if $k8s::manage_packages { 69 | # Ensure conntrack is installed to properly handle networking cleanup 70 | $_conntrack = fact('os.family') ? { 71 | 'Debian' => 'conntrack', 72 | default => 'conntrack-tools', 73 | } 74 | stdlib::ensure_packages([$_conntrack,]) 75 | } 76 | 77 | if $manage_crictl { 78 | include k8s::install::crictl 79 | } 80 | if $manage_kubelet { 81 | include k8s::node::kubelet 82 | } 83 | if $manage_proxy { 84 | include k8s::node::kube_proxy 85 | } 86 | if $manage_simple_cni { 87 | include k8s::node::simple_cni 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /manifests/repo.pp: -------------------------------------------------------------------------------- 1 | # @summary Handles repositories for the container runtime 2 | # 3 | # @param manage_container_manager Whether to add the CRI-o repository or not 4 | # @param container_manager The name of the container manager 5 | # @param major_version The major version of Kubernetes to deploy repos for 6 | # @param core_package_base The url base of the k8s core packages 7 | # @param crio_package_base The url base of the cri-o packages 8 | # 9 | class k8s::repo ( 10 | Boolean $manage_container_manager = $k8s::manage_container_manager, 11 | K8s::Container_runtimes $container_manager = $k8s::container_manager, 12 | String[1] $major_version = $k8s::version.split('\.')[0, 2].join('.'), 13 | String[1] $core_package_base = 'https://pkgs.k8s.io/core:/stable', 14 | String[1] $crio_package_base = 'https://download.opensuse.org/repositories/isv:/cri-o:/stable', 15 | ) { 16 | case fact('os.family') { 17 | 'Debian': { 18 | $core_url = "${core_package_base}:/v${major_version}/deb" 19 | $crio_url = "${crio_package_base}:/v${major_version}/deb" 20 | 21 | apt::source { 'libcontainers:stable': 22 | ensure => absent, 23 | } 24 | apt::source { 'k8s-core': 25 | location => $core_url, 26 | repos => '/', 27 | release => '', 28 | key => { 29 | name => 'k8s-core-apt-keyring.asc', 30 | source => "${core_url}/Release.key", 31 | }, 32 | } 33 | 34 | if $manage_container_manager and $container_manager == 'crio' { 35 | apt::source { 'libcontainers:stable:cri-o': 36 | ensure => absent, 37 | } 38 | apt::source { 'k8s-crio': 39 | location => $crio_url, 40 | repos => '/', 41 | release => '', 42 | key => { 43 | name => 'k8s-crio-apt-keyring.asc', 44 | source => "${crio_url}/Release.key", 45 | }, 46 | } 47 | ~> exec { 'Fix conmon upgrade collision': 48 | command => 'dpkg --no-triggers --force depends -r conmon', 49 | onlyif => 'dpkg -S /usr/libexec/crio/conmon | grep "conmon:"', 50 | refreshonly => true, 51 | path => fact('path'), 52 | } 53 | } 54 | } 55 | 'RedHat': { 56 | $core_url = "${core_package_base}:/v${major_version}/rpm" 57 | $crio_url = "${crio_package_base}:/v${major_version}/rpm" 58 | 59 | yumrepo { 'libcontainers:stable': 60 | ensure => absent, 61 | } 62 | yumrepo { 'k8s-core': 63 | descr => 'Stable releases of Kubernetes', 64 | baseurl => $core_url, 65 | gpgcheck => 1, 66 | gpgkey => "${core_url}/repodata/repomd.xml.key", 67 | } 68 | 69 | if $manage_container_manager { 70 | case $container_manager { 71 | 'crio': { 72 | yumrepo { 'libcontainers:stable:cri-o': 73 | ensure => absent, 74 | } 75 | yumrepo { 'k8s-crio': 76 | descr => 'Stable releases of CRI-o', 77 | baseurl => $crio_url, 78 | gpgcheck => 1, 79 | gpgkey => "${crio_url}/repodata/repomd.xml.key", 80 | } 81 | } 82 | 'containerd': { 83 | yumrepo { 'docker-ce-stable': 84 | descr => 'Docker CE Stable - $basearch', 85 | baseurl => 'https://download.docker.com/linux/centos/$releasever/$basearch/stable', 86 | gpgcheck => 1, 87 | gpgkey => 'https://download.docker.com/linux/centos/gpg', 88 | } 89 | } 90 | default: {} 91 | } 92 | } 93 | } 94 | default: {} 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /manifests/install/container_runtime.pp: -------------------------------------------------------------------------------- 1 | # @summary Manages the installation of a container runtime / CRI 2 | # 3 | # @param manage_repo Whether to manage the repo or not 4 | # @param container_manager The CRI implementation to install 5 | # @param crio_package The CRI-o package name 6 | # @param containerd_package The containerd package name 7 | # @param runc_version The runc version 8 | # @param package_ensure The ensure value to set on the cri package 9 | # 10 | class k8s::install::container_runtime ( 11 | Boolean $manage_repo = $k8s::manage_repo, 12 | K8s::Container_runtimes $container_manager = $k8s::container_manager, 13 | Optional[String[1]] $crio_package = $k8s::crio_package, 14 | Optional[String[1]] $containerd_package = $k8s::containerd_package, 15 | String[1] $runc_version = $k8s::runc_version, 16 | String[1] $package_ensure = installed, 17 | ) { 18 | case $container_manager { 19 | 'crio': { 20 | if fact('os.family') == 'Debian' { 21 | # Prior to 1.28 crio depended on runc https://github.com/cri-o/cri-o/issues/8626 22 | if versioncmp('1.27', $k8s::version) >= 0 { 23 | # This is required for cri-o < 1.28, but it is not guaranteed to be a dependency of the package 24 | package { 'runc': 25 | ensure => $runc_version, 26 | } 27 | 28 | # Avoid a potential packaging issue 29 | file { ['/usr/lib/cri-o-runc/sbin', '/usr/lib/cri-o-runc']: 30 | ensure => directory, 31 | } 32 | 33 | file { '/usr/lib/cri-o-runc/sbin/runc': 34 | ensure => link, 35 | target => '/usr/sbin/runc', 36 | replace => false, 37 | } 38 | } 39 | } elsif fact('os.family') == 'Suse' { 40 | file { '/usr/libexec/crio': 41 | ensure => directory, 42 | } 43 | } 44 | $pkg = pick($crio_package, 'cri-o') 45 | 46 | file { '/usr/libexec/crio/conmon': 47 | ensure => link, 48 | target => '/usr/bin/conmon', 49 | replace => false, 50 | require => Package['k8s container manager'], 51 | } 52 | 53 | file { '/etc/cni/net.d/100-crio-bridge.conf': 54 | ensure => absent, 55 | require => Package['k8s container manager'], 56 | } 57 | 58 | file { ['/etc/crio', '/etc/crio/crio.conf.d']: 59 | ensure => directory; 60 | } 61 | file { 'K8s crio cgroup manager': 62 | path => '/etc/crio/crio.conf.d/10-systemd.conf', 63 | content => "[crio.runtime]\ncgroup_manager = \"systemd\"", 64 | # TODO - Necessary/wanted to force it? 65 | # notify => Service[crio], 66 | } 67 | } 68 | 'containerd': { 69 | file { '/etc/containerd': 70 | ensure => directory, 71 | } 72 | -> file { '/etc/containerd/config.toml': 73 | ensure => file, 74 | source => 'puppet:///modules/k8s/containerd/config.toml', 75 | notify => Service['containerd'], 76 | } 77 | -> service { 'containerd': 78 | ensure => running, 79 | require => Package['k8s container manager'], 80 | } 81 | 82 | $pkg = pick($containerd_package, 'containerd') 83 | } 84 | default: {} 85 | } 86 | 87 | package { 'k8s container manager': 88 | ensure => $package_ensure, 89 | name => $pkg, 90 | } 91 | 92 | file { 93 | default: 94 | ensure => directory; 95 | 96 | '/usr/share/containers/': ; 97 | '/usr/share/containers/oci/': ; 98 | '/usr/share/containers/oci/hooks.d': ; 99 | } 100 | 101 | if $manage_repo { 102 | require k8s::repo 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/kubectl_apply_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'puppet' 5 | 6 | describe Puppet::Type.type(:kubectl_apply) do 7 | let(:resource) do 8 | Puppet::Type.type(:kubectl_apply).new( 9 | name: 'bootstrap-token-example', 10 | namespace: 'kube-system', 11 | 12 | api_version: 'v1', 13 | kind: 'Secret', 14 | 15 | content: { 16 | 'token-id': 'id', 17 | 'token-secret': 'secret', 18 | 'usage-bootstrap-authentication': 'true', 19 | } 20 | ) 21 | end 22 | 23 | context 'resource defaults' do 24 | it { expect(resource[:kubeconfig]).to eq nil } 25 | it { expect(resource[:update]).to eq true } 26 | end 27 | 28 | %w[ 29 | simplename 30 | default-token-6mqpl 31 | metrics-server-7cb45bbfd5-gz4t6 32 | ].each do |name| 33 | it 'accepts valid names' do 34 | expect { resource[:resource_name] = name }.not_to raise_error 35 | end 36 | end 37 | 38 | [ 39 | 'CamelCasedName', 40 | 'name-with space', 41 | 'snake_cased_name', 42 | 'fqdn.like/name', 43 | ].each do |name| 44 | it 'rejects invalid names' do 45 | expect { resource[:resource_name] = name }.to raise_error(Puppet::ResourceError, %r{Resource name must be valid}) 46 | end 47 | end 48 | 49 | %w[ 50 | default 51 | kube-system 52 | some-ridiculously-long-name-thats-still-inside-of-the-limitations-kubernetes-has 53 | ].each do |name| 54 | it 'accepts valid namespaces' do 55 | expect { resource[:namespace] = name }.not_to raise_error 56 | end 57 | end 58 | 59 | [ 60 | 'CamelCasedName', 61 | 'name-with space', 62 | 'snake_cased_name', 63 | 'fqdn.like/name', 64 | ].each do |name| 65 | it 'rejects invalid namespaces' do 66 | expect { resource[:namespace] = name }.to raise_error(Puppet::Error, %r{Namespace must be valid}) 67 | end 68 | end 69 | 70 | it 'rejects too long namespaces' do 71 | expect { resource[:namespace] = 'x' * 254 }.to raise_error(Puppet::Error, %r{Namespace must be valid}) 72 | end 73 | 74 | it 'verify resource[:kubeconfig] is absolute filepath' do 75 | expect { resource[:kubeconfig] = 'relative/file' }.to raise_error(Puppet::Error, %r{Kubeconfig path must be fully qualified}) 76 | end 77 | 78 | it 'verify resource[:content] is a hash' do 79 | expect { resource[:content] = [] }.to raise_error(Puppet::Error, %r{Content must be a valid content hash}) 80 | end 81 | 82 | it 'verify resource[:content] is safe' do 83 | expect { resource[:content] = { 'apiVersion' => 'v2' } }.to raise_error(Puppet::Error, %r{Can't specify apiVersion or kind in content}) 84 | expect { resource[:content] = { 'kind' => 'something' } }.to raise_error(Puppet::Error, %r{Can't specify apiVersion or kind in content}) 85 | end 86 | 87 | describe 'file autorequire' do 88 | let(:file_resource) { Puppet::Type.type(:file).new(name: '/root/.kube/config') } 89 | let(:kubectl_apply_resource) do 90 | described_class.new( 91 | name: 'blah', 92 | namespace: 'default', 93 | api_version: 'v1', 94 | kind: 'ConfigMap', 95 | kubeconfig: '/root/.kube/config' 96 | ) 97 | end 98 | 99 | let(:auto_req) do 100 | catalog = Puppet::Resource::Catalog.new 101 | catalog.add_resource file_resource 102 | catalog.add_resource kubectl_apply_resource 103 | 104 | kubectl_apply_resource.autorequire 105 | end 106 | 107 | it 'creates relationship' do 108 | expect(auto_req.size).to be 1 109 | end 110 | 111 | it 'links to file resource' do 112 | expect(auto_req[0].target).to eql kubectl_apply_resource 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /spec/classes/install/container_runtime_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::install::container_runtime' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | class { 'k8s': 9 | manage_container_manager => false, 10 | } 11 | PUPPET 12 | end 13 | 14 | on_supported_os.each do |os, os_facts| 15 | context "on #{os}" do 16 | let(:facts) { os_facts } 17 | 18 | it { is_expected.to compile } 19 | it { is_expected.to contain_package('k8s container manager') } 20 | it { is_expected.to contain_file('/usr/share/containers/').with(ensure: 'directory') } 21 | it { is_expected.to contain_file('/usr/share/containers/oci/').with(ensure: 'directory') } 22 | it { is_expected.to contain_file('/usr/share/containers/oci/hooks.d').with(ensure: 'directory') } 23 | 24 | context 'when ensure set and package_manager == crio' do 25 | let(:params) do 26 | { 27 | package_ensure: '1.32.0', 28 | container_manager: 'crio', 29 | } 30 | end 31 | 32 | it { is_expected.to contain_package('k8s container manager').with_ensure('1.32.0') } 33 | 34 | it { is_expected.to contain_file('/etc/crio').with(ensure: 'directory') } 35 | it { is_expected.to contain_file('/etc/crio/crio.conf.d').with(ensure: 'directory') } 36 | it { is_expected.to contain_file('/etc/cni/net.d/100-crio-bridge.conf').with(ensure: 'absent') } 37 | it { is_expected.to contain_file('K8s crio cgroup manager').with(path: '/etc/crio/crio.conf.d/10-systemd.conf') } 38 | 39 | it { 40 | is_expected.to contain_file('/usr/libexec/crio/conmon').with( 41 | ensure: 'link', 42 | replace: false, 43 | target: '/usr/bin/conmon' 44 | ) 45 | } 46 | 47 | it { is_expected.not_to contain_package 'runc' } if os_facts.dig('os', 'family') == 'Debian' 48 | it { 49 | is_expected.to contain_file('/usr/libexec/crio').with(ensure: 'directory') if os_facts.dig('os', 'family') == 'Suse' 50 | } 51 | end 52 | 53 | context 'when ensure set and k8s version < 1.28 and package_manager == crio' do 54 | let(:params) do 55 | { 56 | package_ensure: '1.26.0', 57 | container_manager: 'crio', 58 | } 59 | end 60 | 61 | it { is_expected.to contain_package('k8s container manager').with_ensure('1.26.0') } 62 | 63 | if os_facts.dig('os', 'family') == 'Debian' 64 | it { is_expected.to contain_package 'runc' } 65 | it { is_expected.to contain_file('/usr/lib/cri-o-runc').with(ensure: 'directory') } 66 | it { is_expected.to contain_file('/usr/lib/cri-o-runc/sbin').with(ensure: 'directory') } 67 | 68 | it { 69 | is_expected.to contain_file('/usr/lib/cri-o-runc/sbin/runc').with( 70 | ensure: 'link', 71 | replace: false, 72 | target: '/usr/sbin/runc' 73 | ) 74 | } 75 | end 76 | end 77 | 78 | context 'when ensure set and and package_manager == containerd' do 79 | let(:params) do 80 | { 81 | package_ensure: '1.32.0', 82 | container_manager: 'containerd', 83 | } 84 | end 85 | 86 | it { is_expected.to contain_package('k8s container manager').with_ensure('1.32.0') } 87 | 88 | if os_facts.dig('os', 'family') == 'Debian' 89 | it { is_expected.to contain_package 'runc' } 90 | 91 | it { is_expected.to contain_file('/etc/containerd').with(ensure: 'directory') } 92 | it { is_expected.to contain_file('/etc/containerd/config.toml') } 93 | it { is_expected.to contain_service('containerd').with(ensure: 'running') } 94 | end 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/kubeconfig/kubectl_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'mkmf' 5 | 6 | # To remove mkmf log files; 7 | module MakeMakefile::Logging 8 | def self.open 9 | yield 10 | end 11 | 12 | def self.postpone 13 | yield File.open('/dev/null', 'wb') 14 | end 15 | 16 | def self.message(*_); end 17 | end 18 | 19 | kubectl_provider = Puppet::Type.type(:kubeconfig).provider(:kubectl) 20 | 21 | RSpec.describe kubectl_provider do 22 | it_behaves_like 'a kubeconfig provider', kubectl_provider 23 | 24 | describe 'kubectl provider' do 25 | let(:tmpfile) do 26 | tmpfilename('kubeconfig_test') 27 | end 28 | 29 | let(:name) { tmpfile } 30 | let(:resource_properties) do 31 | { 32 | name: name, 33 | server: 'https://kubernetes.home.lan:6443', 34 | 35 | current_context: 'default', 36 | } 37 | end 38 | let(:resource) { Puppet::Type::Kubeconfig.new(resource_properties) } 39 | let(:provider) { kubectl_provider.new(resource) } 40 | 41 | let(:default_kubeconfig) do 42 | { 43 | 'apiVersion' => 'v1', 44 | 'clusters' => [ 45 | { 46 | 'name' => 'default', 47 | 'cluster' => { 48 | 'server' => 'https://kubernetes.home.lan:6443', 49 | }, 50 | }, 51 | ], 52 | 'contexts' => [ 53 | { 54 | 'name' => 'default', 55 | 'context' => { 56 | 'cluster' => 'default', 57 | 'namespace' => 'default', 58 | 'user' => 'default', 59 | }, 60 | }, 61 | ], 62 | 'users' => [ 63 | { 64 | 'name' => 'default', 65 | 'user' => {}, 66 | }, 67 | ], 68 | 'current-context' => 'default', 69 | 'kind' => 'Config', 70 | } 71 | end 72 | 73 | before do 74 | resource.provider = provider 75 | end 76 | 77 | context 'without a local kubectl binary' do 78 | it 'creates default config, updates components' do 79 | expect(provider).to receive(:kubectl).with( 80 | '--kubeconfig', 81 | resource[:path], 82 | 'config', 83 | 'set-cluster', 84 | resource[:cluster], 85 | "--server=#{resource[:server]}" 86 | ) 87 | expect(provider).to receive(:kubectl).with( 88 | '--kubeconfig', 89 | resource[:path], 90 | 'config', 91 | 'set-context', 92 | resource[:context], 93 | "--cluster=#{resource[:cluster]}", 94 | "--user=#{resource[:user]}", 95 | "--namespace=#{resource[:namespace]}" 96 | ) 97 | expect(provider).to receive(:kubectl).with( 98 | '--kubeconfig', 99 | resource[:path], 100 | 'config', 101 | 'set-credentials', 102 | resource[:user] 103 | ) 104 | expect(provider).to receive(:kubectl).with( 105 | '--kubeconfig', 106 | resource[:path], 107 | 'config', 108 | 'use-context', 109 | resource[:current_context] 110 | ) 111 | expect(FileUtils).to receive(:chown).with(resource[:owner], resource[:group], resource[:path]) 112 | expect(FileUtils).to receive(:chmod).with(0o600, resource[:path]) 113 | 114 | provider.create 115 | end 116 | end 117 | 118 | context 'when applied with kubectl binary', if: find_executable('kubectl') do 119 | it 'applies correctly with minimal config' do 120 | allow(Puppet::Util::Storage).to receive(:store) 121 | 122 | catalog = Puppet::Resource::Catalog.new 123 | catalog.add_resource(resource) 124 | catalog.apply 125 | 126 | data = Psych.load(File.read(tmpfile)) 127 | expect(data.slice(*default_kubeconfig.keys)).to eq default_kubeconfig 128 | end 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /manifests/install/cni_plugins.pp: -------------------------------------------------------------------------------- 1 | # @summary Manages the installation of CNI plugins 2 | # 3 | # @param ensure Set ensure for installation or deinstallation 4 | # @param method The installation method to use 5 | # @param version The version of CNI plugins to install - if applicable 6 | # @param download_url_template Template string for the cni_plugins download url 7 | # @param package_name Package name for the CNI plugins, will use OS default if omitted 8 | # 9 | class k8s::install::cni_plugins ( 10 | K8s::Ensure $ensure = $k8s::ensure, 11 | String[1] $version = 'v1.6.2', 12 | String[1] $method = $k8s::native_packaging, 13 | String[1] $download_url_template = 'https://github.com/containernetworking/plugins/releases/download/%{version}/cni-plugins-linux-%{arch}-%{version}.tgz', 14 | Optional[String[1]] $package_name = undef, 15 | ) { 16 | file { 17 | default: 18 | ensure => directory; 19 | 20 | '/etc/cni': ; 21 | '/etc/cni/net.d': ; 22 | '/opt/cni': ; 23 | } 24 | 25 | case $method { 26 | 'tarball', 'loose': { 27 | $_url = k8s::format_url($download_url_template, { 28 | version => $version, 29 | }) 30 | $_target = "/opt/k8s/cni-${version}"; 31 | $_tarball_target = '/opt/k8s/archives'; 32 | 33 | file { $_target: 34 | ensure => stdlib::ensure($ensure, 'directory'), 35 | } 36 | if $ensure == present { 37 | # Store the cni plugin version in a static fact, to retain the plugin directory for copying from on upgrades 38 | file { '/etc/facter/facts.d/cni_plugins_version.txt': 39 | ensure => file, 40 | content => "cni_plugins_version=${version}", 41 | require => File['/opt/cni/bin'], 42 | } 43 | if fact('cni_plugins_version') and fact('cni_plugins_version') != $version { 44 | $_old_target = "/opt/k8s/cni-${fact('cni_plugins_version')}" 45 | file { $_old_target: 46 | ensure => directory, 47 | } 48 | 49 | exec { 'Retain custom CNI binaries': 50 | command => "cp --no-clobber '${_old_target}'/* '${_target}'", 51 | path => fact('path'), 52 | refreshonly => true, 53 | subscribe => File['/opt/cni/bin'], 54 | } 55 | } 56 | } 57 | 58 | archive { 'cni-plugins': 59 | ensure => $ensure, 60 | path => "${_tarball_target}/cni-plugins-linux-${version}.tgz", 61 | source => $_url, 62 | extract => true, 63 | extract_path => $_target, 64 | creates => "${_target}/bridge", 65 | cleanup => true, 66 | } 67 | 68 | file { '/opt/cni/bin': 69 | ensure => stdlib::ensure($ensure, 'link'), 70 | mode => '0755', 71 | replace => true, 72 | force => true, 73 | target => $_target, 74 | require => Archive['cni-plugins'], 75 | } 76 | } 77 | 'package': { 78 | if $k8s::manage_repo or $package_name == 'kubernetes-cni' { 79 | $_package_name = pick($package_name, 'kubernetes-cni') 80 | } else { 81 | if fact('os.family') == 'suse' { 82 | $_package_name = pick($package_name, 'cni-plugins') 83 | } else { 84 | $_package_name = pick($package_name, 'containernetworking-plugins') 85 | } 86 | 87 | if fact('os.family') == 'RedHat' { 88 | $_target = '/usr/libexec/cni' 89 | } else { 90 | $_target = '/usr/lib/cni' 91 | } 92 | 93 | file { '/opt/cni/bin': 94 | ensure => link, 95 | target => $_target, 96 | require => Package[$_package_name], 97 | } 98 | } 99 | stdlib::ensure_packages([$_package_name,]) 100 | 101 | if $k8s::manage_repo { 102 | Class['k8s::repo'] -> Package[$_package_name] 103 | } 104 | } 105 | default: { 106 | fail("install method ${method} not supported") 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /manifests/server/scheduler.pp: -------------------------------------------------------------------------------- 1 | # @summary Installs and configures a Kubernetes scheduler 2 | # @api private 3 | # 4 | # @param ensure Whether the scheduler should be configured. 5 | # @param control_plane_url The URL of the Kubernetes API server. 6 | # @param arguments Additional arguments to pass to the scheduler. 7 | # @param cert_path The path to the directory containing the TLS certificates. 8 | # @param ca_cert The path to the CA certificate. 9 | # @param cert The path to the scheduler certificate. 10 | # @param key The path to the scheduler key. 11 | # @param container_registry The container registry to pull images from. 12 | # @param container_image The container image to use for the scheduler. 13 | # @param container_image_tag The container image tag to use for the scheduler. 14 | # 15 | class k8s::server::scheduler ( 16 | K8s::Ensure $ensure = $k8s::server::ensure, 17 | 18 | Stdlib::HTTPUrl $control_plane_url = $k8s::control_plane_url, 19 | 20 | Hash[String, Data] $arguments = {}, 21 | 22 | Stdlib::Unixpath $cert_path = $k8s::server::tls::cert_path, 23 | Stdlib::Unixpath $ca_cert = $k8s::server::tls::ca_cert, 24 | Stdlib::Unixpath $cert = "${cert_path}/kube-scheduler.pem", 25 | Stdlib::Unixpath $key = "${cert_path}/kube-scheduler.key", 26 | 27 | String[1] $container_registry = $k8s::container_registry, 28 | String[1] $container_image = 'kube-scheduler', 29 | Optional[String[1]] $container_image_tag = $k8s::container_image_tag, 30 | ) { 31 | assert_private() 32 | 33 | k8s::binary { 'kube-scheduler': 34 | ensure => $ensure, 35 | } 36 | 37 | $_kubeconfig = '/srv/kubernetes/kube-scheduler.kubeconf' 38 | if $k8s::packaging != 'container' { 39 | $_addn_args = { 40 | kubeconfig => $_kubeconfig, 41 | } 42 | } else { 43 | $_addn_args = {} 44 | } 45 | 46 | $_args = k8s::format_arguments({ 47 | leader_elect => true, 48 | } + $_addn_args + $arguments) 49 | 50 | if $k8s::packaging == 'container' { 51 | fail('Not implemented yet') 52 | $_image = "${container_registry}/${container_image}:${pick($container_image_tag, "v${k8s::version}")}" 53 | kubectl_apply { 'kube-scheduler': 54 | kubeconfig => '/root/.kube/config', 55 | api_version => 'apps/v1', 56 | kind => 'Deployment', 57 | namespace => 'kube-system', 58 | content => {}, 59 | } 60 | } else { 61 | kubeconfig { $_kubeconfig: 62 | ensure => $ensure, 63 | owner => $k8s::user, 64 | group => $k8s::group, 65 | server => $control_plane_url, 66 | current_context => 'default', 67 | 68 | ca_cert => $ca_cert, 69 | client_cert => $cert, 70 | client_key => $key, 71 | } 72 | file { "${k8s::sysconfig_path}/kube-scheduler": 73 | content => epp('k8s/sysconfig.epp', { 74 | comment => 'Kubernetes Scheduler configuration', 75 | environment_variables => { 76 | 'KUBE_SCHEDULER_ARGS' => $_args.join(' '), 77 | }, 78 | }), 79 | notify => Service['kube-scheduler'], 80 | } 81 | systemd::unit_file { 'kube-scheduler.service': 82 | ensure => $ensure, 83 | content => epp('k8s/service.epp', { 84 | name => 'kube-scheduler', 85 | 86 | desc => 'Kubernetes Scheduler', 87 | doc => 'https://github.com/GoogleCloudPlatform/kubernetes', 88 | 89 | dir => '/srv/kubernetes', 90 | bin => 'kube-scheduler', 91 | needs => ['kube-apiserver.service'], 92 | user => $k8s::user, 93 | group => $k8s::group, 94 | }), 95 | require => [ 96 | File["${k8s::sysconfig_path}/kube-scheduler"], 97 | User[$k8s::user], 98 | ], 99 | notify => Service['kube-scheduler'], 100 | } 101 | service { 'kube-scheduler': 102 | ensure => stdlib::ensure($ensure, 'service'), 103 | enable => true, 104 | subscribe => K8s::Binary['kube-scheduler'], 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/kubeconfig_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'puppet' 5 | 6 | describe Puppet::Type.type(:kubeconfig) do 7 | let(:resource) do 8 | Puppet::Type.type(:kubeconfig).new( 9 | path: '/tmp/kubeconfig', 10 | server: 'https://kubernetes.home.lan:6443' 11 | ) 12 | end 13 | 14 | context 'resource defaults' do 15 | it { expect(resource[:path]).to eq '/tmp/kubeconfig' } 16 | it { expect(resource[:name]).to eq '/tmp/kubeconfig' } 17 | it { expect(resource[:cluster]).to eq 'default' } 18 | it { expect(resource[:context]).to eq 'default' } 19 | it { expect(resource[:user]).to eq 'default' } 20 | it { expect(resource[:namespace]).to eq 'default' } 21 | it { expect(resource[:skip_tls_verify]).not_to eq :true } 22 | it { expect(resource[:embed_certs]).to eq :true } 23 | it { expect(resource[:mode]).to eq '0600' } 24 | end 25 | 26 | it 'verify resource[:path] is absolute filepath' do 27 | expect { resource[:path] = 'relative/file' }.to raise_error(Puppet::Error, %r{File paths must be fully qualified, not 'relative/file'}) 28 | end 29 | 30 | it 'verify resource[:ca_cert] is absolute filepath' do 31 | expect { resource[:ca_cert] = 'relative/file' }.to raise_error(Puppet::Error, %r{File paths must be fully qualified, not 'relative/file'}) 32 | end 33 | 34 | it 'verify resource[:client_cert] is absolute filepath' do 35 | expect { resource[:client_cert] = 'relative/file' }.to raise_error(Puppet::Error, %r{File paths must be fully qualified, not 'relative/file'}) 36 | end 37 | 38 | it 'verify resource[:client_key] is absolute filepath' do 39 | expect { resource[:client_key] = 'relative/file' }.to raise_error(Puppet::Error, %r{File paths must be fully qualified, not 'relative/file'}) 40 | end 41 | 42 | it 'verify resource[:token_file] is absolute filepath' do 43 | expect { resource[:token_file] = 'relative/file' }.to raise_error(Puppet::Error, %r{File paths must be fully qualified, not 'relative/file'}) 44 | end 45 | 46 | describe 'with numeric mode' do 47 | let(:resource) do 48 | Puppet::Type.type(:kubeconfig).new( 49 | path: '/tmp/kubeconfig', 50 | server: 'https://kubernetes.home.lan:6443', 51 | mode: '0640' 52 | ) 53 | end 54 | 55 | it 'is munged to 0640' do 56 | expect(resource[:mode]).to eq '0640' 57 | end 58 | end 59 | 60 | describe 'with symbolic mode' do 61 | let(:resource) do 62 | Puppet::Type.type(:kubeconfig).new( 63 | path: '/tmp/kubeconfig', 64 | server: 'https://kubernetes.home.lan:6443', 65 | mode: 'u=rw,g=r' 66 | ) 67 | end 68 | 69 | it 'is munged to 0640' do 70 | expect(resource[:mode]).to eq '0640' 71 | end 72 | end 73 | 74 | describe 'with complex symbolic mode' do 75 | let(:resource) do 76 | Puppet::Type.type(:kubeconfig).new( 77 | path: '/tmp/kubeconfig', 78 | server: 'https://kubernetes.home.lan:6443', 79 | mode: 'u-x+rw,g-rwx,o-rwx' 80 | ) 81 | end 82 | 83 | it 'is munged to 0600' do 84 | expect(resource[:mode]).to eq '0600' 85 | end 86 | end 87 | 88 | describe 'archive autorequire' do 89 | let(:file_resource) { Puppet::Type.type(:file).new(name: '/tmp') } 90 | let(:kubeconfig_resource) do 91 | described_class.new( 92 | path: '/tmp/kubeconfig', 93 | server: 'https://kubernetes.home.lan:6443' 94 | ) 95 | end 96 | 97 | let(:auto_req) do 98 | catalog = Puppet::Resource::Catalog.new 99 | catalog.add_resource file_resource 100 | catalog.add_resource kubeconfig_resource 101 | 102 | kubeconfig_resource.autorequire 103 | end 104 | 105 | it 'creates relationship' do 106 | expect(auto_req.size).to be 1 107 | end 108 | 109 | it 'links to archive resource' do 110 | expect(auto_req[0].target).to eql kubeconfig_resource 111 | end 112 | 113 | it 'autorequires parent directory' do 114 | expect(auto_req[0].source).to eql file_resource 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /spec/classes/server/controller_manager_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::controller_manager' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | function assert_private() {} 9 | function puppetdb_query(String[1] $data) { 10 | return [ 11 | { 12 | certname => 'node.example.com', 13 | parameters => { 14 | advertise_client_urls => 'https://node.example.com:2380' 15 | } 16 | } 17 | ] 18 | } 19 | 20 | include ::k8s 21 | class { '::k8s::server': 22 | manage_etcd => true, 23 | manage_certs => true, 24 | manage_components => false, 25 | manage_resources => false, 26 | node_on_server => false, 27 | } 28 | PUPPET 29 | end 30 | 31 | on_supported_os.each do |os, os_facts| 32 | context "on #{os}" do 33 | let(:facts) { os_facts } 34 | 35 | it { is_expected.to compile } 36 | 37 | it { is_expected.to contain_kubeconfig('/srv/kubernetes/kube-controller-manager.kubeconf') } 38 | 39 | it { is_expected.not_to contain_file('/etc/kubernetes/manifests/kube-controller-manager.yaml') } 40 | 41 | it do 42 | sysconf = '/etc/sysconfig' 43 | sysconf = '/etc/default' if os_facts['os']['family'] == 'Debian' 44 | 45 | is_expected.to contain_file(File.join(sysconf, 'kube-controller-manager')). 46 | with_content( 47 | <<~SYSCONF 48 | ### NB: File managed by Puppet. 49 | ### Any changes will be overwritten. 50 | # 51 | ## Kubernetes Controller Manager configuration 52 | # 53 | 54 | KUBE_CONTROLLER_MANAGER_ARGS="--allocate-node-cidrs=true --controllers=*,bootstrapsigner,tokencleaner --cluster-cidr=10.0.0.0/16 --service-cluster-ip-range=10.1.0.0/24 --cluster-signing-cert-file=/etc/kubernetes/certs/ca.pem --cluster-signing-key-file=/etc/kubernetes/certs/ca.key --leader-elect=true --root-ca-file=/etc/kubernetes/certs/ca.pem --service-account-private-key-file=/etc/kubernetes/certs/service-account.key --kubeconfig=/srv/kubernetes/kube-controller-manager.kubeconf" 55 | SYSCONF 56 | ).that_notifies('Service[kube-controller-manager]') 57 | end 58 | 59 | it { is_expected.to contain_systemd__unit_file('kube-controller-manager.service').that_notifies('Service[kube-controller-manager]') } 60 | 61 | it do 62 | is_expected.to contain_service('kube-controller-manager').with( 63 | ensure: 'running', 64 | enable: true 65 | ) 66 | end 67 | 68 | context 'with dual-stack' do 69 | let(:params) do 70 | { 71 | cluster_cidr: [ 72 | '10.0.0.0/16', 73 | 'fc00:cafe:42:0::/64', 74 | ], 75 | service_cluster_cidr: [ 76 | '10.1.0.0/24', 77 | 'fc00:cafe:42:1::/64', 78 | ] 79 | } 80 | end 81 | 82 | it do 83 | sysconf = '/etc/sysconfig' 84 | sysconf = '/etc/default' if os_facts['os']['family'] == 'Debian' 85 | 86 | is_expected.to contain_file(File.join(sysconf, 'kube-controller-manager')). 87 | with_content( 88 | <<~SYSCONF 89 | ### NB: File managed by Puppet. 90 | ### Any changes will be overwritten. 91 | # 92 | ## Kubernetes Controller Manager configuration 93 | # 94 | 95 | KUBE_CONTROLLER_MANAGER_ARGS="--allocate-node-cidrs=true --controllers=*,bootstrapsigner,tokencleaner --cluster-cidr=10.0.0.0/16,fc00:cafe:42:0::/64 --service-cluster-ip-range=10.1.0.0/24,fc00:cafe:42:1::/64 --cluster-signing-cert-file=/etc/kubernetes/certs/ca.pem --cluster-signing-key-file=/etc/kubernetes/certs/ca.key --leader-elect=true --root-ca-file=/etc/kubernetes/certs/ca.pem --service-account-private-key-file=/etc/kubernetes/certs/service-account.key --kubeconfig=/srv/kubernetes/kube-controller-manager.kubeconf" 96 | SYSCONF 97 | ).that_notifies('Service[kube-controller-manager]') 98 | end 99 | end 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /manifests/server/tls/cert.pp: -------------------------------------------------------------------------------- 1 | # @summary Generates and signs a TLS certificate 2 | # 3 | # @param addn_names The additional names for the certificate 4 | # @param ca_cert The path to the CA certificate 5 | # @param ca_key The path to the CA key 6 | # @param cert The path to the certificate file 7 | # @param cert_path The path to the directory where the certificate will be stored 8 | # @param config The path to the OpenSSL config file 9 | # @param csr The path to the CSR file 10 | # @param distinguished_name The distinguished name for the certificate 11 | # @param ensure Whether the certificate should be present or absent 12 | # @param extended_key_usage The extended key usage for the certificate 13 | # @param group The group of the certificate files 14 | # @param key The path to the key file 15 | # @param key_bits The number of bits in the key 16 | # @param owner The owner of the certificate files 17 | # @param valid_days The number of days the certificate should be valid 18 | # 19 | define k8s::server::tls::cert ( 20 | Hash[String, String] $distinguished_name, 21 | Stdlib::Unixpath $cert_path, 22 | Stdlib::Unixpath $ca_key, 23 | Stdlib::Unixpath $ca_cert, 24 | 25 | K8s::Ensure $ensure = present, 26 | 27 | Integer[512] $key_bits = 2048, 28 | Integer[1] $valid_days = 10000, 29 | K8s::Extended_key_usage $extended_key_usage = ['clientAuth'], 30 | 31 | K8s::TLS_altnames $addn_names = [], 32 | 33 | Stdlib::Unixpath $config = "${cert_path}/${title}.cnf", 34 | Stdlib::Unixpath $key = "${cert_path}/${title}.key", 35 | Stdlib::Unixpath $csr = "${cert_path}/${title}.csr", 36 | Stdlib::Unixpath $cert = "${cert_path}/${title}.pem", 37 | 38 | String[1] $owner = 'root', 39 | String[1] $group = 'root', 40 | ) { 41 | $_ip_altnames = $addn_names.filter |$ip| { 42 | $ip =~ Stdlib::IP::Address 43 | } 44 | $_dns_altnames = ($addn_names - $_ip_altnames).filter |$dns| { 45 | $dns =~ Stdlib::Fqdn 46 | } 47 | 48 | file { $config: 49 | ensure => $ensure, 50 | owner => $owner, 51 | group => $group, 52 | content => epp('k8s/server/tls/openssl.cnf.epp', { 53 | extended_key_usage => $extended_key_usage, 54 | distinguished_name => $distinguished_name, 55 | dns_altnames => $_dns_altnames, 56 | ip_altnames => $_ip_altnames, 57 | }), 58 | } 59 | ~> Exec <| title == "Create K8s ${title} CSR" |> 60 | 61 | if $ensure == 'present' { 62 | Package <| title == 'openssl' |> 63 | -> exec { 64 | "Create K8s ${title} key": 65 | command => "openssl genrsa -out '${key}' ${key_bits}; echo > '${cert}'", 66 | unless => "openssl pkey -in '${key}' -text | grep '${key_bits} bit'", 67 | path => $facts['path'], 68 | before => File[$key], 69 | notify => Exec["Create K8s ${title} CSR"]; 70 | 71 | "Create K8s ${title} CSR": 72 | command => "openssl req -new -key '${key}' \ 73 | -out '${csr}' -config '${$config}'; echo > '${cert}'", 74 | path => $facts['path'], 75 | refreshonly => true, 76 | notify => Exec["Sign K8s ${title} cert"], 77 | require => File[$key], 78 | before => File[$csr]; 79 | 80 | "Sign K8s ${title} cert": 81 | command => "openssl x509 -req -in '${csr}' \ 82 | -CA '${ca_cert}' -CAkey '${ca_key}' -CAcreateserial \ 83 | -out '${cert}' -days '${valid_days}' \ 84 | -extensions v3_req -extfile '${config}'", 85 | unless => "openssl verify -CAfile '${ca_cert}' '${cert}'", 86 | path => $facts['path'], 87 | require => [ 88 | File[$csr], 89 | File[$key], 90 | ], 91 | before => File[$cert]; 92 | } 93 | File <| title == $ca_key or title == $ca_cert |> -> Exec["Sign K8s ${title} cert"] 94 | } 95 | 96 | if !defined(File[$key]) { 97 | file { $key: 98 | ensure => $ensure, 99 | owner => $owner, 100 | group => $group, 101 | mode => '0600', 102 | replace => false, 103 | } 104 | } 105 | if !defined(File[$cert]) { 106 | file { $cert: 107 | ensure => $ensure, 108 | owner => $owner, 109 | group => $group, 110 | mode => '0640', 111 | replace => false, 112 | } 113 | } 114 | if !defined(File[$csr]) { 115 | file { $csr: 116 | ensure => $ensure, 117 | owner => $owner, 118 | group => $group, 119 | mode => '0640', 120 | replace => false, 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /spec/classes/server/resources/flannel_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'k8s::server::resources::flannel' do 6 | let(:pre_condition) do 7 | <<~PUPPET 8 | function assert_private() {} 9 | 10 | include ::k8s 11 | class { '::k8s::server': 12 | manage_etcd => true, 13 | manage_certs => true, 14 | manage_components => false, 15 | manage_resources => false, 16 | node_on_server => false, 17 | } 18 | class { 'k8s::server::resources': 19 | manage_flannel => false, 20 | } 21 | PUPPET 22 | end 23 | 24 | on_supported_os.each do |os, os_facts| 25 | context "on #{os}" do 26 | let(:facts) { os_facts } 27 | let(:params) { { cluster_cidr: '10.0.0.0/16' } } 28 | 29 | let(:cni_conf) do 30 | { 31 | name: 'cbr0', 32 | cniVersion: '0.3.1', 33 | plugins: [ 34 | { 35 | type: 'flannel', 36 | delegate: { 37 | hairpinMode: true, 38 | isDefaultGateway: true 39 | } 40 | }, 41 | { 42 | type: 'portmap', 43 | capabilities: { 44 | portMappings: true 45 | } 46 | } 47 | ] 48 | }.to_json 49 | end 50 | let(:net_conf) do 51 | { 52 | Network: '10.0.0.0/16', 53 | EnableIPv4: true, 54 | EnableIPv6: false, 55 | Backend: { 56 | Type: 'vxlan' 57 | } 58 | }.to_json 59 | end 60 | let(:daemonset) { subject.call.resource('kubectl_apply', 'flannel DaemonSet') } 61 | 62 | let(:content) do 63 | { 64 | 'metadata' => { 65 | 'labels' => { 66 | 'tier' => 'node', 67 | 'k8s-app' => 'flannel', 68 | 'kubernetes.io/managed-by' => 'puppet', 69 | } 70 | }, 71 | 'data' => { 72 | 'cni-conf.json' => cni_conf, 73 | 'net-conf.json' => net_conf, 74 | } 75 | } 76 | end 77 | 78 | it { is_expected.to compile } 79 | 80 | it { is_expected.to contain_kubectl_apply('flannel ServiceAccount') } 81 | it { is_expected.to contain_kubectl_apply('flannel ClusterRole') } 82 | it { is_expected.to contain_kubectl_apply('flannel ClusterRoleBinding') } 83 | it { is_expected.to contain_kubectl_apply('flannel DaemonSet') } 84 | 85 | describe 'with single-stack IPv4' do 86 | it do 87 | is_expected.to contain_kubectl_apply('flannel ConfigMap'). 88 | with_ensure(:present). 89 | with_content(content) 90 | end 91 | end 92 | 93 | describe 'with single-stack IPv6' do 94 | let(:params) { { cluster_cidr: '2001:6b0:ffff::/48' } } 95 | let(:net_conf) do 96 | { 97 | IPv6Network: '2001:6b0:ffff::/48', 98 | EnableIPv4: false, 99 | EnableIPv6: true, 100 | Backend: { 101 | Type: 'vxlan' 102 | } 103 | }.to_json 104 | end 105 | 106 | it do 107 | is_expected.to contain_kubectl_apply('flannel ConfigMap'). 108 | with_ensure(:present). 109 | with_content(content) 110 | end 111 | end 112 | 113 | describe 'with dual-stack IPv4+6' do 114 | let(:params) { { cluster_cidr: ['10.0.0.0/16', '2001:6b0:ffff::/48'] } } 115 | let(:net_conf) do 116 | { 117 | Network: '10.0.0.0/16', 118 | IPv6Network: '2001:6b0:ffff::/48', 119 | EnableIPv4: true, 120 | EnableIPv6: true, 121 | Backend: { 122 | Type: 'vxlan' 123 | } 124 | }.to_json 125 | end 126 | 127 | it do 128 | is_expected.to contain_kubectl_apply('flannel ConfigMap'). 129 | with_ensure(:present). 130 | with_content(content) 131 | end 132 | end 133 | 134 | describe 'without network policy support' do 135 | let(:params) { { netpol: false } } 136 | 137 | it 'does not contain kube-network-policies container' do 138 | expect(daemonset[:content].dig('spec', 'template', 'spec', 'containers').last['name']).not_to eq('kube-network-policies') 139 | end 140 | end 141 | 142 | describe 'with network policy support' do 143 | let(:params) { { netpol: true } } 144 | 145 | it 'contains kube-network-policies container' do 146 | expect(daemonset[:content].dig('spec', 'template', 'spec', 'containers').last['name']).to eq('kube-network-policies') 147 | end 148 | end 149 | end 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /manifests/node/kube_proxy.pp: -------------------------------------------------------------------------------- 1 | # @summary Sets up a on-node kube-proxy instance 2 | # @api private 3 | # 4 | # For most use-cases, running kube-proxy inside the cluster itself is recommended 5 | # 6 | # @param arguments A hash of additional arguments to pass to kube-proxy 7 | # @param auth The authentication method to use for the API server 8 | # @param ca_cert The path to the CA certificate to use for the API server 9 | # @param cert The path to the client certificate to use for the API server 10 | # @param cluster_cidr The CIDR range of the cluster 11 | # @param config A hash of additional configuration options to pass to kube-proxy 12 | # @param control_plane_url The URL of the Kubernetes API server 13 | # @param ensure Whether the kube-proxy service should be configured 14 | # @param key The path to the client key to use for the API server 15 | # @param puppetdb_discovery_tag The tag to use for PuppetDB service discovery 16 | # @param token The token to use for the API server 17 | # 18 | class k8s::node::kube_proxy ( 19 | K8s::Ensure $ensure = $k8s::node::ensure, 20 | 21 | Stdlib::HTTPUrl $control_plane_url = $k8s::node::control_plane_url, 22 | 23 | Hash[String, Data] $config = {}, 24 | Hash[String, Data] $arguments = {}, 25 | String $puppetdb_discovery_tag = $k8s::node::puppetdb_discovery_tag, 26 | 27 | K8s::CIDR $cluster_cidr = $k8s::cluster_cidr, 28 | 29 | K8s::Proxy_auth $auth = $k8s::node::proxy_auth, 30 | 31 | # For cert auth 32 | Optional[Stdlib::Unixpath] $ca_cert = $k8s::node::ca_cert, 33 | Optional[Stdlib::Unixpath] $cert = $k8s::node::proxy_cert, 34 | Optional[Stdlib::Unixpath] $key = $k8s::node::proxy_key, 35 | 36 | # For token and bootstrap auth 37 | Optional[Sensitive[String]] $token = $k8s::node::proxy_token, 38 | ) { 39 | assert_private() 40 | 41 | if $auth == 'incluster' and $k8s::packaging != 'container' { 42 | # If the proxy is set to incluster auth then it will expect to run as a cluster service 43 | $_ensure = absent 44 | } else { 45 | $_ensure = $ensure 46 | } 47 | 48 | k8s::binary { 'kube-proxy': 49 | ensure => $_ensure, 50 | } 51 | 52 | $kubeconfig = '/srv/kubernetes/kube-proxy.kubeconf' 53 | case $auth { 54 | 'token': { 55 | kubeconfig { $kubeconfig: 56 | ensure => $_ensure, 57 | owner => $k8s::user, 58 | group => $k8s::group, 59 | server => $control_plane_url, 60 | token => $token.unwrap, 61 | current_context => 'default', 62 | ca_cert => $ca_cert, 63 | notify => Service['kube-proxy'], 64 | } 65 | } 66 | 'cert': { 67 | kubeconfig { $kubeconfig: 68 | ensure => $_ensure, 69 | owner => $k8s::user, 70 | group => $k8s::group, 71 | server => $control_plane_url, 72 | client_cert => $cert, 73 | client_key => $key, 74 | current_context => 'default', 75 | ca_cert => $ca_cert, 76 | notify => Service['kube-proxy'], 77 | } 78 | } 79 | default: {} 80 | } 81 | 82 | $config_hash = { 83 | 'apiVersion' => 'kubeproxy.config.k8s.io/v1alpha1', 84 | 'kind' => 'KubeProxyConfiguration', 85 | 86 | 'clusterCIDR' => $cluster_cidr, 87 | } + $config 88 | 89 | file { '/etc/kubernetes/kube-proxy.conf': 90 | ensure => $_ensure, 91 | content => to_yaml($config_hash), 92 | owner => $k8s::user, 93 | group => $k8s::group, 94 | notify => Service['kube-proxy'], 95 | } 96 | 97 | $_args = k8s::format_arguments({ 98 | config => '/etc/kubernetes/kube-proxy.conf', 99 | kubeconfig => $kubeconfig, 100 | } + $arguments) 101 | 102 | if $k8s::packaging == 'container' { 103 | } else { 104 | file { "${k8s::sysconfig_path}/kube-proxy": 105 | ensure => $_ensure, 106 | content => epp('k8s/sysconfig.epp', { 107 | comment => 'Kubernetes kube-proxy configuration', 108 | environment_variables => { 109 | 'KUBE_PROXY_ARGS' => $_args.join(' '), 110 | }, 111 | }), 112 | notify => Service['kube-proxy'], 113 | } 114 | 115 | systemd::unit_file { 'kube-proxy.service': 116 | ensure => $_ensure, 117 | content => epp('k8s/service.epp', { 118 | name => 'kube-proxy', 119 | 120 | desc => 'Kubernetes Network Proxy', 121 | doc => 'https://github.com/GoogleCloudPlatform/kubernetes', 122 | bin => 'kube-proxy', 123 | }), 124 | require => [ 125 | File["${k8s::sysconfig_path}/kube-proxy"], 126 | User[$k8s::user], 127 | ], 128 | notify => Service['kube-proxy'], 129 | } 130 | service { 'kube-proxy': 131 | ensure => stdlib::ensure($_ensure, 'service'), 132 | enable => $_ensure == 'present', 133 | subscribe => K8s::Binary['kube-proxy'], 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /manifests/server/controller_manager.pp: -------------------------------------------------------------------------------- 1 | # @summary Installs and configures a Kubernetes controller manager 2 | # @api private 3 | # 4 | # @param arguments Additional arguments to pass to the controller manager. 5 | # @param ca_cert The path to the CA certificate. 6 | # @param ca_key The path to the CA key. 7 | # @param cert The path to the controller manager certificate. 8 | # @param cert_path The path to the TLS certificates. 9 | # @param cluster_cidr The CIDR of the cluster. 10 | # @param container_image The container image to use for the controller manager. 11 | # @param container_image_tag The container image tag to use for the controller manager. 12 | # @param container_registry The container registry to pull the controller manager image from. 13 | # @param control_plane_url The URL of the Kubernetes API server. 14 | # @param ensure Whether the controller manager should be configured. 15 | # @param key The path to the controller manager key. 16 | # @param service_cluster_cidr The CIDR of the service cluster. 17 | # 18 | class k8s::server::controller_manager ( 19 | K8s::Ensure $ensure = $k8s::server::ensure, 20 | 21 | Stdlib::HTTPUrl $control_plane_url = $k8s::control_plane_url, 22 | 23 | Hash[String, Data] $arguments = {}, 24 | 25 | K8s::CIDR $service_cluster_cidr = $k8s::service_cluster_cidr, 26 | K8s::CIDR $cluster_cidr = $k8s::cluster_cidr, 27 | 28 | Stdlib::Unixpath $cert_path = $k8s::server::tls::cert_path, 29 | Stdlib::Unixpath $ca_cert = $k8s::server::tls::ca_cert, 30 | Stdlib::Unixpath $ca_key = $k8s::server::tls::ca_key, 31 | Stdlib::Unixpath $cert = "${cert_path}/kube-controller-manager.pem", 32 | Stdlib::Unixpath $key = "${cert_path}/kube-controller-manager.key", 33 | 34 | String[1] $container_registry = $k8s::container_registry, 35 | String[1] $container_image = 'kube-controller-manager', 36 | Optional[String[1]] $container_image_tag = $k8s::container_image_tag, 37 | ) { 38 | assert_private() 39 | 40 | k8s::binary { 'kube-controller-manager': 41 | ensure => $ensure, 42 | } 43 | $_kubeconfig = '/srv/kubernetes/kube-controller-manager.kubeconf' 44 | 45 | if $k8s::packaging != 'container' { 46 | $_addn_args = { 47 | kubeconfig => $_kubeconfig, 48 | } 49 | } else { 50 | $_addn_args = {} 51 | } 52 | 53 | # For container; 54 | # use_service_account_credentials => true, 55 | $_args = k8s::format_arguments({ 56 | allocate_node_cidrs => true, 57 | controllers => [ 58 | '*', 59 | 'bootstrapsigner', 60 | 'tokencleaner', 61 | ], 62 | cluster_cidr => $cluster_cidr, 63 | service_cluster_ip_range => $service_cluster_cidr, 64 | cluster_signing_cert_file => $ca_cert, 65 | cluster_signing_key_file => $ca_key, 66 | leader_elect => true, 67 | root_ca_file => $ca_cert, 68 | service_account_private_key_file => "${cert_path}/service-account.key", 69 | } + $_addn_args + $arguments) 70 | 71 | if $k8s::packaging == 'container' { 72 | fail('Not implemented yet') 73 | $_image = "${container_registry}/${container_image}:${pick($container_image_tag, "v${k8s::version}")}" 74 | kubectl_apply { 'kube-controller-manager': 75 | kubeconfig => '/root/.kube/config', 76 | api_version => 'apps/v1', 77 | kind => 'Deployment', 78 | namespace => 'kube-system', 79 | content => {}, 80 | } 81 | } else { 82 | kubeconfig { $_kubeconfig: 83 | ensure => $ensure, 84 | owner => $k8s::user, 85 | group => $k8s::group, 86 | server => $control_plane_url, 87 | current_context => 'default', 88 | 89 | ca_cert => $ca_cert, 90 | client_cert => $cert, 91 | client_key => $key, 92 | } 93 | 94 | file { "${k8s::sysconfig_path}/kube-controller-manager": 95 | content => epp('k8s/sysconfig.epp', { 96 | comment => 'Kubernetes Controller Manager configuration', 97 | environment_variables => { 98 | 'KUBE_CONTROLLER_MANAGER_ARGS' => $_args.join(' '), 99 | }, 100 | }), 101 | notify => Service['kube-controller-manager'], 102 | } 103 | systemd::unit_file { 'kube-controller-manager.service': 104 | ensure => $ensure, 105 | content => epp('k8s/service.epp', { 106 | name => 'kube-controller-manager', 107 | 108 | desc => 'Kubernetes Controller Manager', 109 | doc => 'https://github.com/GoogleCloudPlatform/kubernetes', 110 | 111 | dir => '/srv/kubernetes', 112 | bin => 'kube-controller-manager', 113 | needs => ['kube-apiserver.service'], 114 | user => $k8s::user, 115 | group => $k8s::group, 116 | }), 117 | require => [ 118 | File["${k8s::sysconfig_path}/kube-controller-manager"], 119 | User[$k8s::user], 120 | ], 121 | notify => Service['kube-controller-manager'], 122 | } 123 | service { 'kube-controller-manager': 124 | ensure => stdlib::ensure($ensure, 'service'), 125 | enable => true, 126 | subscribe => K8s::Binary['kube-controller-manager'], 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/puppet/type/kubectl_apply.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet/parameter/boolean' 4 | 5 | Puppet::Type.newtype(:kubectl_apply) do 6 | desc <<-DOC 7 | Example: 8 | 9 | To encode the bootstrap token "tokenid.tokensecret" into a Kubernetes secret; 10 | 11 | $tokenid = 'tokenid' 12 | $tokensecret = 'tokensecret' 13 | kubectl_apply { "bootstrap-token-${tokenid}": 14 | namespace => 'kube-system', 15 | kubeconfig => '/root/.kube/config', 16 | 17 | api_version => 'v1, 18 | kind => 'Secret', 19 | 20 | content => { 21 | type => 'bootstrap.kubernetes.io/token', 22 | data => { 23 | 'token-id' => Binary.new($tokenid, '%s'), 24 | 'token-secret' => Binary.new($tokensecret, '%s'), 25 | 'usage-bootstrap-authentication' => 'true', 26 | }, 27 | }, 28 | } 29 | DOC 30 | 31 | ensurable do 32 | desc 'Whether the described resource should be present or absent (default: present)' 33 | 34 | newvalue(:present) do 35 | provider.create unless provider.exists? 36 | end 37 | 38 | newvalue(:absent) do 39 | provider.destroy if provider.exists? 40 | end 41 | 42 | defaultto(:present) 43 | 44 | def change_to_s(currentvalue, newvalue) 45 | if currentvalue == :absent || currentvalue.nil? 46 | if provider.exists_in_cluster 47 | if resource[:show_diff] && provider.resource_diff 48 | "updated #{resource[:kind]} #{resource.nice_name} with #{provider.resource_diff.inspect}" 49 | else 50 | "updated #{resource[:kind]} #{resource.nice_name}" 51 | end 52 | elsif resource[:show_diff] && provider.resource_diff 53 | "created #{resource[:kind]} #{resource.nice_name} with #{provider.resource_diff.inspect}" 54 | else 55 | "created #{resource[:kind]} #{resource.nice_name}" 56 | end 57 | elsif newvalue == :absent 58 | "removed #{resource[:kind]} #{resource.nice_name}" 59 | else 60 | super 61 | end 62 | end 63 | 64 | def retrieve 65 | prov = @resource.provider 66 | raise Puppet::Error, 'Could not find provider' unless prov 67 | 68 | prov.exists? ? :present : :absent 69 | end 70 | end 71 | 72 | # XXX Better way to separate name from Puppet namevar handling? 73 | newparam(:resource_name) do 74 | desc 'The name of the resource' 75 | 76 | validate do |value| 77 | raise Puppet::Error, 'Resource name must be valid' unless value.match? %r{^([a-z0-9][a-z0-9.:-]{0,251}[a-z0-9]|[a-z0-9])$} 78 | end 79 | end 80 | 81 | newparam(:name, namevar: true) do 82 | desc 'The Puppet name of the instance' 83 | end 84 | 85 | newparam(:namespace) do 86 | desc 'The namespace the resource is contained in' 87 | 88 | validate do |value| 89 | raise Puppet::Error, 'Namespace must be valid' unless value.match? %r{^[a-z0-9.-]{0,253}$} 90 | end 91 | end 92 | 93 | newparam(:kubeconfig) do 94 | desc 'The kubeconfig file to use for handling the resource' 95 | 96 | validate do |value| 97 | raise Puppet::Error, 'Kubeconfig path must be fully qualified' unless Puppet::Util.absolute_path?(value) 98 | end 99 | end 100 | newparam(:file) do 101 | desc 'The local file for the resource' 102 | 103 | validate do |value| 104 | raise Puppet::Error, 'File path must be fully qualified' unless Puppet::Util.absolute_path?(value) 105 | end 106 | end 107 | 108 | newparam(:api_version) do 109 | desc 'The apiVersion of the resource' 110 | end 111 | 112 | newparam(:kind) do 113 | desc 'The kind of the resource' 114 | end 115 | 116 | newparam(:update, boolean: true, parent: Puppet::Parameter::Boolean) do 117 | desc 'Whether to update the resource if the content differs' 118 | defaultto(:true) 119 | end 120 | 121 | newparam(:recreate, boolean: true, parent: Puppet::Parameter::Boolean) do 122 | desc 'Should updates be done by removal and recreation' 123 | defaultto(:false) 124 | end 125 | 126 | newparam(:show_diff, boolean: true, parent: Puppet::Parameter::Boolean) do 127 | desc 'Whether to display the difference when the resource changes' 128 | defaultto(:false) 129 | end 130 | 131 | newparam(:content) do 132 | desc 'The resource content, will be used as the base for the resulting Kubernetes resource' 133 | defaultto({}) 134 | 135 | validate do |value| 136 | raise Puppet::Error, 'Content must be a valid content hash' unless value.is_a? Hash 137 | 138 | raise Puppet::Error, "Can't specify apiVersion or kind in content" if %w[apiVersion kind].any? { |key| value.key? key } 139 | end 140 | end 141 | 142 | validate do 143 | self[:resource_name] = self[:name] if self[:resource_name].nil? 144 | 145 | raise Puppet::Error, 'API version is required' unless self[:api_version] 146 | raise Puppet::Error, 'Kind is required' unless self[:kind] 147 | end 148 | 149 | autorequire(:kubeconfig) do 150 | [self[:kubeconfig]] 151 | end 152 | autorequire(:service) do 153 | ['kube-apiserver'] 154 | end 155 | autorequire(:exec) do 156 | ['k8s apiserver wait online'] 157 | end 158 | autorequire(:file) do 159 | [ 160 | self[:kubeconfig], 161 | self[:file], 162 | ] 163 | end 164 | autorequire(:k8s__binary) do 165 | ['kubectl'] 166 | end 167 | 168 | def nice_name 169 | return self[:name] unless self[:namespace] 170 | 171 | "#{self[:namespace]}/#{self[:name]}" 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /manifests/binary.pp: -------------------------------------------------------------------------------- 1 | # @summary Deploys a Kubernetes binary 2 | # 3 | # @param ensure Whether the binary should be present or absent 4 | # @param version The version to deploy 5 | # @param packaging The packaging method to use 6 | # @param target The directory to deploy the binary to 7 | # @param tarball_target The directory to download tarballs to 8 | # @param binary_target The directory to place active binary symlinks into 9 | # @param active Whether the binary should be active 10 | # @param manage_system Whether the binary should be installed system-wide 11 | # @param component The component to deploy 12 | # 13 | define k8s::binary ( 14 | K8s::Ensure $ensure = $k8s::ensure, 15 | String[1] $version = $k8s::version, 16 | String[1] $packaging = $k8s::packaging, 17 | 18 | Stdlib::Unixpath $target = "/opt/k8s/${$version}", 19 | Stdlib::Unixpath $tarball_target = '/opt/k8s/archives', 20 | Stdlib::Unixpath $binary_target = '/opt/k8s/bin', 21 | 22 | Boolean $active = true, 23 | Boolean $manage_system = true, 24 | 25 | Optional[String] $component = undef, 26 | ) { 27 | if $name in ['kubelet', 'kube-proxy'] { 28 | $_component = pick($component, 'node') 29 | } elsif $name in ['kube-apiserver', 'kube-controller-manager', 'kube-scheduler'] { 30 | $_component = pick($component, 'server') 31 | } else { 32 | $_component = pick($component, 'client') 33 | } 34 | 35 | if !defined(File[$target]) { 36 | file { $target: 37 | ensure => stdlib::ensure($ensure, 'directory'), 38 | } 39 | } 40 | 41 | # Always install kubelet and kubectl as binaries 42 | if $packaging == 'native' or ($packaging == 'container' and $name in ['kubelet', 'kubectl']) { 43 | $_packaging = $k8s::native_packaging 44 | } else { 45 | $_packaging = $packaging 46 | } 47 | 48 | case $_packaging { 49 | 'container': {} 50 | 'package': { 51 | $_template = $k8s::package_template 52 | $_name = k8s::format_url($_template, { 53 | version => $version, 54 | component => $_component, 55 | }) 56 | package { "kubernetes-${name}": 57 | ensure => $ensure, 58 | name => $_name, 59 | } 60 | 61 | if !defined(File["${target}/${name}"]) { 62 | file { "${target}/${name}": 63 | ensure => $ensure, 64 | mode => '0755', 65 | target => "/usr/bin/${name}", 66 | } 67 | } 68 | } 69 | 'tarball': { 70 | $_template = $k8s::tarball_url_template 71 | $_url = k8s::format_url($_template, { 72 | version => $version, 73 | component => $_component, 74 | }) 75 | $_file = "${tarball_target}/${basename($_url)}" 76 | if !defined(File[$tarball_target]) { 77 | file { $tarball_target: 78 | ensure => stdlib::ensure($ensure, 'directory'), 79 | purge => true, 80 | recurse => true, 81 | } 82 | } 83 | 84 | archive { "${name} from ${_file}": 85 | ensure => $ensure, 86 | path => $_file, 87 | source => $_url, 88 | extract => true, 89 | extract_command => "tar -C '${target}' -xf %s --transform 's/.*\\///' --wildcards '*${name}'", 90 | extract_path => $target, 91 | cleanup => true, 92 | creates => "${target}/${name}", 93 | } 94 | if !defined(File["${target}/${name}"]) { 95 | file { "${target}/${name}": 96 | ensure => $ensure, 97 | mode => '0755', 98 | replace => false, 99 | } 100 | } 101 | } 102 | 'loose': { 103 | $_template = $k8s::native_url_template 104 | $_url = k8s::format_url($_template, { 105 | version => $version, 106 | component => $_component, 107 | binary => $name, 108 | }) 109 | file { "${target}/${name}": 110 | ensure => $ensure, 111 | mode => '0755', 112 | source => $_url, 113 | } 114 | } 115 | 'hyperkube': { 116 | $_template = $k8s::native_url_template 117 | $_url = k8s::format_url($_template, { 118 | version => $version, 119 | component => $_component, 120 | binary => $k8s::hyperkube_name, 121 | }) 122 | if !defined(File["${target}/${k8s::hyperkube_name}"]) { 123 | file { "${target}/${k8s::hyperkube_name}": 124 | ensure => $ensure, 125 | mode => '0755', 126 | source => $_url, 127 | } 128 | } 129 | file { "${target}/${name}": 130 | ensure => $ensure, 131 | mode => '0755', 132 | target => "${target}/${k8s::hyperkube_name}", 133 | } 134 | } 135 | 'manual': { 136 | # User is expected to have created ${target}/${name} now 137 | File<| $title == "${target}/${name}" |> {} 138 | } 139 | default: { 140 | fail('Invalid packaging specified') 141 | } 142 | } 143 | 144 | if $active and $_packaging =~ Enum['tarball', 'loose', 'hyperkube'] { 145 | file { "${binary_target}/${name}": 146 | ensure => link, 147 | mode => '0755', 148 | replace => true, 149 | target => "${target}/${name}", 150 | } 151 | } 152 | 153 | if $manage_system and $active and $packaging != 'container' and $_packaging != 'manual' and !defined(File["/usr/bin/${name}"]) { 154 | if $packaging == 'package' { 155 | file { "/usr/bin/${name}": 156 | ensure => $ensure, 157 | mode => '0755', 158 | replace => false, 159 | } 160 | } else { 161 | file { "/usr/bin/${name}": 162 | ensure => link, 163 | mode => '0755', 164 | replace => true, 165 | target => "${target}/${name}", 166 | } 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/puppet/type/kubeconfig.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Puppet::Type.newtype(:kubeconfig) do 4 | desc <<-DOC 5 | Example: 6 | 7 | kubeconfig { '/var/lib/kubernetes/utility.conf': 8 | ca_cert => '/etc/kubernetes.ca.pem', 9 | token => 'utility-token', 10 | } 11 | DOC 12 | 13 | ensurable do 14 | desc 'Whether the kubeconfig should be present or absent (default: present)' 15 | 16 | newvalue(:present) do 17 | provider.create unless provider.exists? 18 | end 19 | 20 | newvalue(:absent) do 21 | provider.destroy 22 | end 23 | 24 | defaultto(:present) 25 | 26 | def change_to_s(currentvalue, newvalue) 27 | if currentvalue == :absent || currentvalue.nil? 28 | if provider.exists? && !provider.valid? 29 | 'updated' 30 | else 31 | 'created' 32 | end 33 | elsif newvalue == :absent 34 | 'removed' 35 | else 36 | super 37 | end 38 | end 39 | 40 | def retrieve 41 | prov = @resource.provider 42 | raise Puppet::Error, 'Could not find provider' unless prov 43 | 44 | prov.exists? ? :present : :absent 45 | end 46 | end 47 | 48 | newparam(:path, namevar: true) do 49 | desc 'An arbitrary path used as the identity of the resource.' 50 | 51 | validate do |value| 52 | raise Puppet::Error, "File paths must be fully qualified, not '#{value}'" unless Puppet::Util.absolute_path?(value) 53 | end 54 | end 55 | 56 | newparam(:owner) do 57 | desc 'The owner of the kubeconfig file' 58 | end 59 | newparam(:group) do 60 | desc 'The owner of the kubeconfig file' 61 | end 62 | newparam(:mode) do 63 | require 'puppet/util/symbolic_file_mode' 64 | include Puppet::Util::SymbolicFileMode 65 | 66 | desc 'The access mode of the kubeconfig file' 67 | defaultto '0600' 68 | 69 | validate do |value| 70 | raise Puppet::Error, "The file mode specification must be a string, not '#{value.class.name}'" unless value.is_a? String 71 | raise Puppet::Error, "The file mode specification is invalid: #{value.inspect}" unless value.nil? || valid_symbolic_mode?(value) 72 | end 73 | 74 | munge do |value| 75 | return nil if value.nil? 76 | raise Puppet::Error, "The file mode specification is invalid: #{value.inspect}" unless valid_symbolic_mode?(value) 77 | 78 | "0#{symbolic_mode_to_int(normalize_symbolic_mode(value)).to_s(8)}" 79 | end 80 | 81 | unmunge do |value| 82 | display_mode(value) if value 83 | end 84 | end 85 | 86 | newparam(:cluster) do 87 | desc 'The name of the cluster to manage in the kubeconfig file' 88 | defaultto 'default' 89 | end 90 | newparam(:context) do 91 | desc 'The name of the cluster to manage in the kubeconfig file' 92 | defaultto 'default' 93 | end 94 | newparam(:user) do 95 | desc 'The name of the user to manage in the kubeconfig file' 96 | defaultto 'default' 97 | end 98 | newparam(:namespace) do 99 | desc 'The namespace to default to' 100 | defaultto 'default' 101 | end 102 | newparam(:current_context) do 103 | desc 'The current context to set' 104 | end 105 | 106 | newparam(:server) do 107 | desc 'The server URL for the cluster' 108 | end 109 | 110 | newparam(:skip_tls_verify) do 111 | desc 'Skip verifying the TLS certs for the cluster' 112 | newvalues(true, false) 113 | defaultto false 114 | end 115 | newparam(:tls_server_name) do 116 | desc 'Specify an alternate server name to use for TLS verification' 117 | end 118 | 119 | newparam(:embed_certs) do 120 | desc 'Should the certificate files be embedded into the kubeconfig file' 121 | newvalues(true, false) 122 | defaultto true 123 | end 124 | 125 | newparam(:ca_cert) do 126 | desc 'The path to a CA certificate to include in the kubeconfig' 127 | 128 | validate do |value| 129 | raise Puppet::Error, "File paths must be fully qualified, not '#{value}'" unless Puppet::Util.absolute_path?(value) 130 | end 131 | end 132 | newparam(:client_cert) do 133 | desc 'The path to a client certificate to include in the kubeconfig' 134 | 135 | validate do |value| 136 | raise Puppet::Error, "File paths must be fully qualified, not '#{value}'" unless Puppet::Util.absolute_path?(value) 137 | end 138 | end 139 | newparam(:client_key) do 140 | desc 'The path to a client key to include in the kubeconfig' 141 | 142 | validate do |value| 143 | raise Puppet::Error, "File paths must be fully qualified, not '#{value}'" unless Puppet::Util.absolute_path?(value) 144 | end 145 | end 146 | 147 | newparam(:token) do 148 | desc 'An authentication token for a user' 149 | end 150 | 151 | newparam(:token_file) do 152 | desc 'The path to a file containing an authentication token' 153 | 154 | validate do |value| 155 | raise Puppet::Error, "File paths must be fully qualified, not '#{value}'" unless Puppet::Util.absolute_path?(value) 156 | end 157 | end 158 | 159 | newparam(:username) do 160 | desc 'The username of a user' 161 | end 162 | newparam(:password) do 163 | desc 'The password of a user' 164 | end 165 | 166 | validate do 167 | raise Puppet::Error, "Can't specify both token and token_file for the same kubeconfig entry" if self[:token] && self[:token_file] 168 | raise(Puppet::Error, 'path is a required attribute') unless self[:path] 169 | end 170 | 171 | # Ensure the file or directory exists 172 | autorequire(:file) do 173 | req = [ 174 | self[:path], 175 | Pathname.new(self[:path]).parent.to_s, 176 | ] 177 | if self[:embed_certs] 178 | req += [ 179 | self[:ca_cert], 180 | self[:client_cert], 181 | self[:client_key], 182 | ].compact 183 | end 184 | req += [self[:token_file]].compact 185 | 186 | req 187 | end 188 | end 189 | --------------------------------------------------------------------------------