summaryrefslogtreecommitdiff
path: root/vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/apiserver.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/apiserver.go')
-rw-r--r--vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/apiserver.go469
1 files changed, 469 insertions, 0 deletions
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/apiserver.go b/vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/apiserver.go
new file mode 100644
index 000000000..ece5efd65
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/apiserver.go
@@ -0,0 +1,469 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controlplane
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strconv"
+ "time"
+
+ "sigs.k8s.io/controller-runtime/pkg/internal/testing/addr"
+ "sigs.k8s.io/controller-runtime/pkg/internal/testing/certs"
+ "sigs.k8s.io/controller-runtime/pkg/internal/testing/process"
+)
+
+const (
+ // saKeyFile is the name of the service account signing private key file.
+ saKeyFile = "sa-signer.key"
+ // saKeyFile is the name of the service account signing public key (cert) file.
+ saCertFile = "sa-signer.crt"
+)
+
+// SecureServing provides/configures how the API server serves on the secure port.
+type SecureServing struct {
+ // ListenAddr contains the host & port to serve on.
+ //
+ // Configurable. If unset, it will be defaulted.
+ process.ListenAddr
+ // CA contains the CA that signed the API server's serving certificates.
+ //
+ // Read-only.
+ CA []byte
+ // Authn can be used to provision users, and override what type of
+ // authentication is used to provision users.
+ //
+ // Configurable. If unset, it will be defaulted.
+ Authn
+}
+
+// APIServer knows how to run a kubernetes apiserver.
+type APIServer struct {
+ // URL is the address the ApiServer should listen on for client
+ // connections.
+ //
+ // If set, this will configure the *insecure* serving details.
+ // If unset, it will contain the insecure port if insecure serving is enabled,
+ // and otherwise will contain the secure port.
+ //
+ // If this is not specified, we default to a random free port on localhost.
+ //
+ // Deprecated: use InsecureServing (for the insecure URL) or SecureServing, ideally.
+ URL *url.URL
+
+ // SecurePort is the additional secure port that the APIServer should listen on.
+ //
+ // If set, this will override SecureServing.Port.
+ //
+ // Deprecated: use SecureServing.
+ SecurePort int
+
+ // SecureServing indicates how the API server will serve on the secure port.
+ //
+ // Some parts are configurable. Will be defaulted if unset.
+ SecureServing
+
+ // InsecureServing indicates how the API server will serve on the insecure port.
+ //
+ // If unset, the insecure port will be disabled. Set to an empty struct to get
+ // default values.
+ //
+ // Deprecated: does not work with Kubernetes versions 1.20 and above. Use secure
+ // serving instead.
+ InsecureServing *process.ListenAddr
+
+ // Path is the path to the apiserver binary.
+ //
+ // If this is left as the empty string, we will attempt to locate a binary,
+ // by checking for the TEST_ASSET_KUBE_APISERVER environment variable, and
+ // the default test assets directory. See the "Binaries" section above (in
+ // doc.go) for details.
+ Path string
+
+ // Args is a list of arguments which will passed to the APIServer binary.
+ // Before they are passed on, they will be evaluated as go-template strings.
+ // This means you can use fields which are defined and exported on this
+ // APIServer struct (e.g. "--cert-dir={{ .Dir }}").
+ // Those templates will be evaluated after the defaulting of the APIServer's
+ // fields has already happened and just before the binary actually gets
+ // started. Thus you have access to calculated fields like `URL` and others.
+ //
+ // If not specified, the minimal set of arguments to run the APIServer will
+ // be used.
+ //
+ // They will be loaded into the same argument set as Configure. Each flag
+ // will be Append-ed to the configured arguments just before launch.
+ //
+ // Deprecated: use Configure instead.
+ Args []string
+
+ // CertDir is a path to a directory containing whatever certificates the
+ // APIServer will need.
+ //
+ // If left unspecified, then the Start() method will create a fresh temporary
+ // directory, and the Stop() method will clean it up.
+ CertDir string
+
+ // EtcdURL is the URL of the Etcd the APIServer should use.
+ //
+ // If this is not specified, the Start() method will return an error.
+ EtcdURL *url.URL
+
+ // StartTimeout, StopTimeout specify the time the APIServer is allowed to
+ // take when starting and stoppping before an error is emitted.
+ //
+ // If not specified, these default to 20 seconds.
+ StartTimeout time.Duration
+ StopTimeout time.Duration
+
+ // Out, Err specify where APIServer should write its StdOut, StdErr to.
+ //
+ // If not specified, the output will be discarded.
+ Out io.Writer
+ Err io.Writer
+
+ processState *process.State
+
+ // args contains the structured arguments to use for running the API server
+ // Lazily initialized by .Configure(), Defaulted eventually with .defaultArgs()
+ args *process.Arguments
+}
+
+// Configure returns Arguments that may be used to customize the
+// flags used to launch the API server. A set of defaults will
+// be applied underneath.
+func (s *APIServer) Configure() *process.Arguments {
+ if s.args == nil {
+ s.args = process.EmptyArguments()
+ }
+ return s.args
+}
+
+// Start starts the apiserver, waits for it to come up, and returns an error,
+// if occurred.
+func (s *APIServer) Start() error {
+ if err := s.prepare(); err != nil {
+ return err
+ }
+ return s.processState.Start(s.Out, s.Err)
+}
+
+func (s *APIServer) prepare() error {
+ if err := s.setProcessState(); err != nil {
+ return err
+ }
+ return s.Authn.Start()
+}
+
+// configurePorts configures the serving ports for this API server.
+//
+// Most of this method currently deals with making the deprecated fields
+// take precedence over the new fields.
+func (s *APIServer) configurePorts() error {
+ // prefer the old fields to the new fields if a user set one,
+ // otherwise, default the new fields and populate the old ones.
+
+ // Insecure: URL, InsecureServing
+ if s.URL != nil {
+ s.InsecureServing = &process.ListenAddr{
+ Address: s.URL.Hostname(),
+ Port: s.URL.Port(),
+ }
+ } else if insec := s.InsecureServing; insec != nil {
+ if insec.Port == "" || insec.Address == "" {
+ port, host, err := addr.Suggest("")
+ if err != nil {
+ return fmt.Errorf("unable to provision unused insecure port: %w", err)
+ }
+ s.InsecureServing.Port = strconv.Itoa(port)
+ s.InsecureServing.Address = host
+ }
+ s.URL = s.InsecureServing.URL("http", "")
+ }
+
+ // Secure: SecurePort, SecureServing
+ if s.SecurePort != 0 {
+ s.SecureServing.Port = strconv.Itoa(s.SecurePort)
+ // if we don't have an address, try the insecure address, and otherwise
+ // default to loopback.
+ if s.SecureServing.Address == "" {
+ if s.InsecureServing != nil {
+ s.SecureServing.Address = s.InsecureServing.Address
+ } else {
+ s.SecureServing.Address = "127.0.0.1"
+ }
+ }
+ } else if s.SecureServing.Port == "" || s.SecureServing.Address == "" {
+ port, host, err := addr.Suggest("")
+ if err != nil {
+ return fmt.Errorf("unable to provision unused secure port: %w", err)
+ }
+ s.SecureServing.Port = strconv.Itoa(port)
+ s.SecureServing.Address = host
+ s.SecurePort = port
+ }
+
+ return nil
+}
+
+func (s *APIServer) setProcessState() error {
+ if s.EtcdURL == nil {
+ return fmt.Errorf("expected EtcdURL to be configured")
+ }
+
+ var err error
+
+ // unconditionally re-set this so we can successfully restart
+ // TODO(directxman12): we supported this in the past, but do we actually
+ // want to support re-using an API server object to restart? The loss
+ // of provisioned users is surprising to say the least.
+ s.processState = &process.State{
+ Dir: s.CertDir,
+ Path: s.Path,
+ StartTimeout: s.StartTimeout,
+ StopTimeout: s.StopTimeout,
+ }
+ if err := s.processState.Init("kube-apiserver"); err != nil {
+ return err
+ }
+
+ if err := s.configurePorts(); err != nil {
+ return err
+ }
+
+ // the secure port will always be on, so use that
+ s.processState.HealthCheck.URL = *s.SecureServing.URL("https", "/healthz")
+
+ s.CertDir = s.processState.Dir
+ s.Path = s.processState.Path
+ s.StartTimeout = s.processState.StartTimeout
+ s.StopTimeout = s.processState.StopTimeout
+
+ if err := s.populateAPIServerCerts(); err != nil {
+ return err
+ }
+
+ if s.SecureServing.Authn == nil {
+ authn, err := NewCertAuthn()
+ if err != nil {
+ return err
+ }
+ s.SecureServing.Authn = authn
+ }
+
+ if err := s.Authn.Configure(s.CertDir, s.Configure()); err != nil {
+ return err
+ }
+
+ // NB(directxman12): insecure port is a mess:
+ // - 1.19 and below have the `--insecure-port` flag, and require it to be set to zero to
+ // disable it, otherwise the default will be used and we'll conflict.
+ // - 1.20 requires the flag to be unset or set to zero, and yells at you if you configure it
+ // - 1.24 won't have the flag at all...
+ //
+ // In an effort to automatically do the right thing during this mess, we do feature discovery
+ // on the flags, and hope that we've "parsed" them properly.
+ //
+ // TODO(directxman12): once we support 1.20 as the min version (might be when 1.24 comes out,
+ // might be around 1.25 or 1.26), remove this logic and the corresponding line in API server's
+ // default args.
+ if err := s.discoverFlags(); err != nil {
+ return err
+ }
+
+ s.processState.Args, s.Args, err = process.TemplateAndArguments(s.Args, s.Configure(), process.TemplateDefaults{ //nolint:staticcheck
+ Data: s,
+ Defaults: s.defaultArgs(),
+ MinimalDefaults: map[string][]string{
+ // as per kubernetes-sigs/controller-runtime#641, we need this (we
+ // probably need other stuff too, but this is the only thing that was
+ // previously considered a "minimal default")
+ "service-cluster-ip-range": {"10.0.0.0/24"},
+
+ // we need *some* authorization mode for health checks on the secure port,
+ // so default to RBAC unless the user set something else (in which case
+ // this'll be ignored due to SliceToArguments using AppendNoDefaults).
+ "authorization-mode": {"RBAC"},
+ },
+ })
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// discoverFlags checks for certain flags that *must* be set in certain
+// versions, and *must not* be set in others.
+func (s *APIServer) discoverFlags() error {
+ // Present: <1.24, Absent: >= 1.24
+ present, err := s.processState.CheckFlag("insecure-port")
+ if err != nil {
+ return err
+ }
+
+ if !present {
+ s.Configure().Disable("insecure-port")
+ }
+
+ return nil
+}
+
+func (s *APIServer) defaultArgs() map[string][]string {
+ args := map[string][]string{
+ "service-cluster-ip-range": {"10.0.0.0/24"},
+ "allow-privileged": {"true"},
+ // we're keeping this disabled because if enabled, default SA is
+ // missing which would force all tests to create one in normal
+ // apiserver operation this SA is created by controller, but that is
+ // not run in integration environment
+ "disable-admission-plugins": {"ServiceAccount"},
+ "cert-dir": {s.CertDir},
+ "authorization-mode": {"RBAC"},
+ "secure-port": {s.SecureServing.Port},
+ // NB(directxman12): previously we didn't set the bind address for the secure
+ // port. It *shouldn't* make a difference unless people are doing something really
+ // funky, but if you start to get bug reports look here ;-)
+ "bind-address": {s.SecureServing.Address},
+
+ // required on 1.20+, fine to leave on for <1.20
+ "service-account-issuer": {s.SecureServing.URL("https", "/").String()},
+ "service-account-key-file": {filepath.Join(s.CertDir, saCertFile)},
+ "service-account-signing-key-file": {filepath.Join(s.CertDir, saKeyFile)},
+ }
+ if s.EtcdURL != nil {
+ args["etcd-servers"] = []string{s.EtcdURL.String()}
+ }
+ if s.URL != nil {
+ args["insecure-port"] = []string{s.URL.Port()}
+ args["insecure-bind-address"] = []string{s.URL.Hostname()}
+ } else {
+ // TODO(directxman12): remove this once 1.21 is the lowest version we support
+ // (this might be a while, but this line'll break as of 1.24, so see the comment
+ // in Start
+ args["insecure-port"] = []string{"0"}
+ }
+ return args
+}
+
+func (s *APIServer) populateAPIServerCerts() error {
+ _, statErr := os.Stat(filepath.Join(s.CertDir, "apiserver.crt"))
+ if !os.IsNotExist(statErr) {
+ return statErr
+ }
+
+ ca, err := certs.NewTinyCA()
+ if err != nil {
+ return err
+ }
+
+ servingCerts, err := ca.NewServingCert()
+ if err != nil {
+ return err
+ }
+
+ certData, keyData, err := servingCerts.AsBytes()
+ if err != nil {
+ return err
+ }
+
+ if err := ioutil.WriteFile(filepath.Join(s.CertDir, "apiserver.crt"), certData, 0640); err != nil { //nolint:gosec
+ return err
+ }
+ if err := ioutil.WriteFile(filepath.Join(s.CertDir, "apiserver.key"), keyData, 0640); err != nil { //nolint:gosec
+ return err
+ }
+
+ s.SecureServing.CA = ca.CA.CertBytes()
+
+ // service account signing files too
+ saCA, err := certs.NewTinyCA()
+ if err != nil {
+ return err
+ }
+
+ saCert, saKey, err := saCA.CA.AsBytes()
+ if err != nil {
+ return err
+ }
+
+ if err := ioutil.WriteFile(filepath.Join(s.CertDir, saCertFile), saCert, 0640); err != nil { //nolint:gosec
+ return err
+ }
+ return ioutil.WriteFile(filepath.Join(s.CertDir, saKeyFile), saKey, 0640) //nolint:gosec
+}
+
+// Stop stops this process gracefully, waits for its termination, and cleans up
+// the CertDir if necessary.
+func (s *APIServer) Stop() error {
+ if s.processState.DirNeedsCleaning {
+ s.CertDir = "" // reset the directory if it was randomly allocated, so that we can safely restart
+ }
+ if s.processState != nil {
+ if err := s.processState.Stop(); err != nil {
+ return err
+ }
+ }
+ return s.Authn.Stop()
+}
+
+// APIServerDefaultArgs exposes the default args for the APIServer so that you
+// can use those to append your own additional arguments.
+//
+// Note that these arguments don't handle newer API servers well to due the more
+// complex feature detection neeeded. It's recommended that you switch to .Configure
+// as you upgrade API server versions.
+//
+// Deprecated: use APIServer.Configure().
+var APIServerDefaultArgs = []string{
+ "--advertise-address=127.0.0.1",
+ "--etcd-servers={{ if .EtcdURL }}{{ .EtcdURL.String }}{{ end }}",
+ "--cert-dir={{ .CertDir }}",
+ "--insecure-port={{ if .URL }}{{ .URL.Port }}{{else}}0{{ end }}",
+ "{{ if .URL }}--insecure-bind-address={{ .URL.Hostname }}{{ end }}",
+ "--secure-port={{ if .SecurePort }}{{ .SecurePort }}{{ end }}",
+ // we're keeping this disabled because if enabled, default SA is missing which would force all tests to create one
+ // in normal apiserver operation this SA is created by controller, but that is not run in integration environment
+ "--disable-admission-plugins=ServiceAccount",
+ "--service-cluster-ip-range=10.0.0.0/24",
+ "--allow-privileged=true",
+ // NB(directxman12): we also enable RBAC if nothing else was enabled
+}
+
+// PrepareAPIServer is an internal-only (NEVER SHOULD BE EXPOSED)
+// function that sets up the API server just before starting it,
+// without actually starting it. This saves time on tests.
+//
+// NB(directxman12): do not expose this outside of internal -- it's unsafe to
+// use, because things like port allocation could race even more than they
+// currently do if you later call start!
+func PrepareAPIServer(s *APIServer) error {
+ return s.prepare()
+}
+
+// APIServerArguments is an internal-only (NEVER SHOULD BE EXPOSED)
+// function that sets up the API server just before starting it,
+// without actually starting it. It's public to make testing easier.
+//
+// NB(directxman12): do not expose this outside of internal.
+func APIServerArguments(s *APIServer) []string {
+ return s.processState.Args
+}