mirror of
https://github.com/siderolabs/talos.git
synced 2025-11-28 14:11:15 +01:00
feat: introduce Talos API access from Kubernetes
We add a new CRD, `serviceaccounts.talos.dev` (with `tsa` as short name), and its controller which allows users to get a `Secret` containing a short-lived Talosconfig in their namespaces with the roles they need. Additionally, we introduce the `talosctl inject serviceaccount` command to accept a YAML file with Kubernetes manifests and inject them with Talos service accounts so that they can be directly applied to Kubernetes afterwards. If Talos API access feature is enabled on Talos side, the injected workloads will be able to talk to Talos API. Closes siderolabs/talos#4422. Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
This commit is contained in:
parent
d7be308921
commit
84e712a9f1
@ -847,12 +847,16 @@ func trimVersion(version string) string {
|
||||
}
|
||||
|
||||
func init() {
|
||||
defaultTalosConfig, err := clientconfig.GetDefaultPath()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to find default Talos config path: %s", err)
|
||||
}
|
||||
|
||||
createCmd.Flags().StringVar(&talosconfig, "talosconfig", defaultTalosConfig, "The path to the Talos configuration file")
|
||||
createCmd.Flags().StringVar(
|
||||
&talosconfig,
|
||||
"talosconfig",
|
||||
"",
|
||||
fmt.Sprintf("The path to the Talos configuration file. Defaults to '%s' env variable if set, otherwise '%s' and '%s' in order.",
|
||||
constants.TalosConfigEnvVar,
|
||||
filepath.Join("$HOME", constants.TalosDir, constants.TalosconfigFilename),
|
||||
filepath.Join(constants.ServiceAccountMountPath, constants.TalosconfigFilename),
|
||||
),
|
||||
)
|
||||
createCmd.Flags().StringVar(&nodeImage, "image", helpers.DefaultImage(images.DefaultTalosImageRepository), "the image to use")
|
||||
createCmd.Flags().StringVar(&nodeInstallImage, nodeInstallImageFlag, helpers.DefaultImage(images.DefaultInstallerImageRepository), "the installer image to use")
|
||||
createCmd.Flags().StringVar(&nodeVmlinuzPath, "vmlinuz-path", helpers.ArtifactPath(constants.KernelAssetWithArch), "the compressed kernel image to use")
|
||||
|
||||
14
cmd/talosctl/cmd/mgmt/inject/inject.go
Normal file
14
cmd/talosctl/cmd/mgmt/inject/inject.go
Normal file
@ -0,0 +1,14 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package inject
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// Cmd represents the debug command.
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "inject",
|
||||
Short: "Inject Talos API resources into Kubernetes manifests",
|
||||
Long: ``,
|
||||
}
|
||||
68
cmd/talosctl/cmd/mgmt/inject/serviceaccount.go
Normal file
68
cmd/talosctl/cmd/mgmt/inject/serviceaccount.go
Normal file
@ -0,0 +1,68 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package inject
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/kubernetes/inject"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
var serviceAccountCmdFlags struct {
|
||||
file string
|
||||
roles []string
|
||||
}
|
||||
|
||||
var serviceAccountCmd = &cobra.Command{
|
||||
Use: fmt.Sprintf("%s [--roles='<ROLE_1>,<ROLE_2>'] -f <manifest.yaml>", constants.ServiceAccountResourceSingular),
|
||||
Aliases: []string{constants.ServiceAccountResourceShortName},
|
||||
Short: "Inject Talos API ServiceAccount into Kubernetes manifests",
|
||||
Example: fmt.Sprintf(
|
||||
`talosctl inject %[1]s --roles="os:admin" -f deployment.yaml > deployment-injected.yaml
|
||||
|
||||
Alternatively, stdin can be piped to the command:
|
||||
cat deployment.yaml | talosctl inject %[1]s --roles="os:admin" -f - > deployment-injected.yaml
|
||||
`,
|
||||
constants.ServiceAccountResourceSingular,
|
||||
),
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
var err error
|
||||
|
||||
if serviceAccountCmdFlags.file == "" {
|
||||
return cmd.Help()
|
||||
}
|
||||
|
||||
reader := os.Stdin
|
||||
|
||||
if serviceAccountCmdFlags.file != "-" {
|
||||
reader, err = os.Open(serviceAccountCmdFlags.file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
injectedYaml, err := inject.ServiceAccount(reader, serviceAccountCmdFlags.roles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(injectedYaml))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
serviceAccountCmd.Flags().StringVarP(&serviceAccountCmdFlags.file, "file", "f", "",
|
||||
fmt.Sprintf("file with Kubernetes manifests to be injected with %s", constants.ServiceAccountResourceKind))
|
||||
serviceAccountCmd.Flags().StringSliceVarP(&serviceAccountCmdFlags.roles, "roles", "r", []string{"os:reader"},
|
||||
fmt.Sprintf("roles to add to the generated %s manifests", constants.ServiceAccountResourceKind))
|
||||
Cmd.AddCommand(serviceAccountCmd)
|
||||
}
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt/cluster"
|
||||
"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt/debug"
|
||||
"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt/gen"
|
||||
"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt/inject"
|
||||
)
|
||||
|
||||
// Commands is a list of commands published by the package.
|
||||
@ -28,4 +29,5 @@ func init() {
|
||||
addCommand(cluster.Cmd)
|
||||
addCommand(gen.Cmd)
|
||||
addCommand(debug.Cmd)
|
||||
addCommand(inject.Cmd)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@ -14,7 +15,7 @@ import (
|
||||
"github.com/talos-systems/talos/cmd/talosctl/cmd/mgmt"
|
||||
"github.com/talos-systems/talos/cmd/talosctl/cmd/talos"
|
||||
"github.com/talos-systems/talos/pkg/cli"
|
||||
clientconfig "github.com/talos-systems/talos/pkg/machinery/client/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands.
|
||||
@ -30,12 +31,16 @@ var rootCmd = &cobra.Command{
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() error {
|
||||
defaultTalosConfig, err := clientconfig.GetDefaultPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&talos.Talosconfig, "talosconfig", defaultTalosConfig, "The path to the Talos configuration file")
|
||||
rootCmd.PersistentFlags().StringVar(
|
||||
&talos.Talosconfig,
|
||||
"talosconfig",
|
||||
"",
|
||||
fmt.Sprintf("The path to the Talos configuration file. Defaults to '%s' env variable if set, otherwise '%s' and '%s' in order.",
|
||||
constants.TalosConfigEnvVar,
|
||||
filepath.Join("$HOME", constants.TalosDir, constants.TalosconfigFilename),
|
||||
filepath.Join(constants.ServiceAccountMountPath, constants.TalosconfigFilename),
|
||||
),
|
||||
)
|
||||
rootCmd.PersistentFlags().StringVar(&talos.Cmdcontext, "context", "", "Context to be used in command")
|
||||
rootCmd.PersistentFlags().StringSliceVarP(&talos.Nodes, "nodes", "n", []string{}, "target the specified nodes")
|
||||
rootCmd.PersistentFlags().StringSliceVarP(&talos.Endpoints, "endpoints", "e", []string{}, "override default endpoints in Talos configuration")
|
||||
|
||||
@ -14,6 +14,32 @@ preface = """\
|
||||
"""
|
||||
|
||||
[notes]
|
||||
[notes.api-access-from-kubernetes]
|
||||
title = "Talos API access from Kubernetes"
|
||||
description = """\
|
||||
Talos now supports access to its API from within Kubernetes. It can be configured in the machine config as below:
|
||||
```yaml
|
||||
machine:
|
||||
features:
|
||||
kubernetesTalosAPIAccess:
|
||||
enabled: true
|
||||
allowedRoles:
|
||||
- os:reader
|
||||
allowedKubernetesNamespaces:
|
||||
- kube-system
|
||||
```
|
||||
|
||||
This feature introduces a new custom resource definition, `serviceaccounts.talos.dev`.
|
||||
Creating custom resources of this type will provide credentials to access Talos API from within Kubernetes.
|
||||
|
||||
The new CLI subcommand `talosctl inject serviceaccount` can be used to configure Kubernetes manifests with Talos service accounts as below:
|
||||
```
|
||||
talosctl inject serviceaccount -f manifests.yaml > manifests-injected.yaml
|
||||
kubectl apply -f manifests-injected.yaml
|
||||
```
|
||||
|
||||
See [documentation](https://www.talos.dev/v1.2/advanced/configuration/talos-api-access-from-k8s/) for more details.
|
||||
"""
|
||||
|
||||
[notes.seccomp]
|
||||
title = "Seccomp Profiles"
|
||||
|
||||
@ -165,6 +165,8 @@ func (ctrl *ManifestController) render(cfg k8s.BootstrapManifestsConfigSpec, scr
|
||||
KubernetesTalosAPIServiceNamespace string
|
||||
|
||||
ApidPort int
|
||||
|
||||
TalosServiceAccount TalosServiceAccount
|
||||
}{
|
||||
BootstrapManifestsConfigSpec: cfg,
|
||||
Secrets: scrt,
|
||||
@ -173,6 +175,15 @@ func (ctrl *ManifestController) render(cfg k8s.BootstrapManifestsConfigSpec, scr
|
||||
KubernetesTalosAPIServiceNamespace: constants.KubernetesTalosAPIServiceNamespace,
|
||||
|
||||
ApidPort: constants.ApidPort,
|
||||
|
||||
TalosServiceAccount: TalosServiceAccount{
|
||||
Group: constants.ServiceAccountResourceGroup,
|
||||
Version: constants.ServiceAccountResourceVersion,
|
||||
Kind: constants.ServiceAccountResourceKind,
|
||||
ResourceSingular: constants.ServiceAccountResourceSingular,
|
||||
ResourcePlural: constants.ServiceAccountResourcePlural,
|
||||
ShortName: constants.ServiceAccountResourceShortName,
|
||||
},
|
||||
}
|
||||
|
||||
type manifestDesc struct {
|
||||
@ -226,6 +237,7 @@ func (ctrl *ManifestController) render(cfg k8s.BootstrapManifestsConfigSpec, scr
|
||||
defaultManifests = append(defaultManifests,
|
||||
[]manifestDesc{
|
||||
{"12-talos-api-service", talosAPIService},
|
||||
{"13-talos-service-account-crd", talosServiceAccountCRDTemplate},
|
||||
}...,
|
||||
)
|
||||
}
|
||||
@ -276,3 +288,14 @@ func (ctrl *ManifestController) teardownAll(ctx context.Context, r controller.Ru
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TalosServiceAccount is a struct used by the template engine which contains the needed variables to
|
||||
// be able to construct the Talos Service Account CRD.
|
||||
type TalosServiceAccount struct {
|
||||
Group string
|
||||
Version string
|
||||
Kind string
|
||||
ResourceSingular string
|
||||
ResourcePlural string
|
||||
ShortName string
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/siderolabs/go-pointer"
|
||||
"go.etcd.io/etcd/client/v3/concurrency"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@ -155,7 +154,7 @@ func (ctrl *ManifestApplyController) Run(ctx context.Context, r controller.Runti
|
||||
return fmt.Errorf("error building dynamic client: %w", err)
|
||||
}
|
||||
|
||||
if err = ctrl.etcdLock(ctx, logger, func() error {
|
||||
if err = etcd.WithLock(ctx, constants.EtcdTalosManifestApplyMutex, logger, func() error {
|
||||
return ctrl.apply(ctx, logger, mapper, dyn, manifests)
|
||||
}); err != nil {
|
||||
return err
|
||||
@ -176,36 +175,6 @@ func (ctrl *ManifestApplyController) Run(ctx context.Context, r controller.Runti
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *ManifestApplyController) etcdLock(ctx context.Context, logger *zap.Logger, f func() error) error {
|
||||
etcdClient, err := etcd.NewLocalClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating etcd client: %w", err)
|
||||
}
|
||||
|
||||
defer etcdClient.Close() //nolint:errcheck
|
||||
|
||||
session, err := concurrency.NewSession(etcdClient.Client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating etcd session: %w", err)
|
||||
}
|
||||
|
||||
defer session.Close() //nolint:errcheck
|
||||
|
||||
mutex := concurrency.NewMutex(session, constants.EtcdTalosManifestApplyMutex)
|
||||
|
||||
logger.Debug("waiting for mutex")
|
||||
|
||||
if err := mutex.Lock(ctx); err != nil {
|
||||
return fmt.Errorf("error acquiring mutex: %w", err)
|
||||
}
|
||||
|
||||
logger.Debug("mutex acquired")
|
||||
|
||||
defer mutex.Unlock(ctx) //nolint:errcheck
|
||||
|
||||
return f()
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (ctrl *ManifestApplyController) apply(ctx context.Context, logger *zap.Logger, mapper *restmapper.DeferredDiscoveryRESTMapper, dyn dynamic.Interface, manifests resource.List) error {
|
||||
// flatten list of objects to be applied
|
||||
|
||||
@ -718,3 +718,43 @@ spec:
|
||||
protocol: TCP
|
||||
targetPort: {{ .ApidPort }}
|
||||
`)
|
||||
|
||||
// talosServiceAccountCRDTemplate is the template of the CRD which
|
||||
// allows injecting Talos with credentials into the Kubernetes cluster.
|
||||
var talosServiceAccountCRDTemplate = []byte(`apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: {{ .TalosServiceAccount.ResourcePlural }}.{{ .TalosServiceAccount.Group }}
|
||||
spec:
|
||||
conversion:
|
||||
strategy: None
|
||||
group: {{ .TalosServiceAccount.Group }}
|
||||
names:
|
||||
kind: {{ .TalosServiceAccount.Kind }}
|
||||
listKind: {{ .TalosServiceAccount.Kind }}List
|
||||
plural: {{ .TalosServiceAccount.ResourcePlural }}
|
||||
singular: {{ .TalosServiceAccount.ResourceSingular }}
|
||||
shortNames:
|
||||
- {{ .TalosServiceAccount.ShortName }}
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: {{ .TalosServiceAccount.Version }}
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
roles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
status:
|
||||
type: object
|
||||
properties:
|
||||
failureReason:
|
||||
type: string
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
`)
|
||||
|
||||
@ -39,57 +39,7 @@ func (ctrl *EndpointController) Name() string {
|
||||
|
||||
// Inputs implements controller.Controller interface.
|
||||
func (ctrl *EndpointController) Inputs() []controller.Input {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Outputs implements controller.Controller interface.
|
||||
func (ctrl *EndpointController) Outputs() []controller.Output {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run implements controller.Controller interface.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (ctrl *EndpointController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
for {
|
||||
if err := r.UpdateInputs([]controller.Input{
|
||||
{
|
||||
Namespace: config.NamespaceName,
|
||||
Type: kubeaccess.ConfigType,
|
||||
ID: pointer.To(kubeaccess.ConfigID),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-r.EventCh():
|
||||
}
|
||||
|
||||
kubeaccessConfig, err := r.Get(ctx, kubeaccess.NewConfig(config.NamespaceName, kubeaccess.ConfigID).Metadata())
|
||||
if err != nil {
|
||||
if !state.IsNotFoundError(err) {
|
||||
return fmt.Errorf("error fetching kubeaccess config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if kubeaccessConfig == nil || !kubeaccessConfig.(*kubeaccess.Config).TypedSpec().Enabled {
|
||||
// disabled, nothing to do
|
||||
continue
|
||||
}
|
||||
|
||||
if err = ctrl.reconcile(ctx, r, logger); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (ctrl *EndpointController) reconcile(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
if err := r.UpdateInputs([]controller.Input{
|
||||
return []controller.Input{
|
||||
{
|
||||
Namespace: config.NamespaceName,
|
||||
Type: kubeaccess.ConfigType,
|
||||
@ -107,12 +57,18 @@ func (ctrl *EndpointController) reconcile(ctx context.Context, r controller.Runt
|
||||
Type: k8s.EndpointType,
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r.QueueReconcile()
|
||||
// Outputs implements controller.Controller interface.
|
||||
func (ctrl *EndpointController) Outputs() []controller.Output {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run implements controller.Controller interface.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (ctrl *EndpointController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
for {
|
||||
select {
|
||||
case <-r.EventCh():
|
||||
@ -128,8 +84,8 @@ func (ctrl *EndpointController) reconcile(ctx context.Context, r controller.Runt
|
||||
}
|
||||
|
||||
if kubeaccessConfig == nil || !kubeaccessConfig.(*kubeaccess.Config).TypedSpec().Enabled {
|
||||
// disabled, bail out
|
||||
return nil
|
||||
// disabled, do not do anything
|
||||
continue
|
||||
}
|
||||
|
||||
endpointResources, err := r.List(ctx, resource.NewMetadata(k8s.ControlPlaneNamespaceName, k8s.EndpointType, "", resource.VersionUndefined))
|
||||
|
||||
@ -0,0 +1,204 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package kubeaccess
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/safe"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/siderolabs/go-pointer"
|
||||
"github.com/talos-systems/crypto/x509"
|
||||
"go.uber.org/zap"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/controllers/kubeaccess/serviceaccount"
|
||||
"github.com/talos-systems/talos/internal/pkg/etcd"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/kubeaccess"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/secrets"
|
||||
)
|
||||
|
||||
// CRDController manages Kubernetes endpoints resource for Talos API endpoints.
|
||||
type CRDController struct{}
|
||||
|
||||
// Name implements controller.Controller interface.
|
||||
func (ctrl *CRDController) Name() string {
|
||||
return "kubeaccess.CRDController"
|
||||
}
|
||||
|
||||
// Inputs implements controller.Controller interface.
|
||||
func (ctrl *CRDController) Inputs() []controller.Input {
|
||||
return []controller.Input{
|
||||
{
|
||||
Namespace: config.NamespaceName,
|
||||
Type: kubeaccess.ConfigType,
|
||||
ID: pointer.To(kubeaccess.ConfigID),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
Namespace: secrets.NamespaceName,
|
||||
Type: secrets.KubernetesType,
|
||||
ID: pointer.To(secrets.KubernetesID),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
Namespace: secrets.NamespaceName,
|
||||
Type: secrets.OSRootType,
|
||||
ID: pointer.To(secrets.OSRootID),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Outputs implements controller.Controller interface.
|
||||
func (ctrl *CRDController) Outputs() []controller.Output {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run implements controller.Controller interface.
|
||||
//
|
||||
//nolint:gocyclo,cyclop
|
||||
func (ctrl *CRDController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
var crdControllerCtxCancel context.CancelFunc
|
||||
|
||||
crdControllerErrCh := make(chan error, 1)
|
||||
|
||||
stopCRDController := func() {
|
||||
if crdControllerCtxCancel != nil {
|
||||
crdControllerCtxCancel()
|
||||
|
||||
<-crdControllerErrCh
|
||||
|
||||
crdControllerCtxCancel = nil
|
||||
}
|
||||
}
|
||||
|
||||
defer stopCRDController()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil //nolint:govet
|
||||
case <-r.EventCh():
|
||||
case err := <-crdControllerErrCh:
|
||||
if crdControllerCtxCancel != nil {
|
||||
crdControllerCtxCancel()
|
||||
}
|
||||
|
||||
crdControllerCtxCancel = nil
|
||||
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
return fmt.Errorf("error from crd controller: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
kubeaccessConfig, err := safe.ReaderGet[*kubeaccess.Config](ctx, r, kubeaccess.NewConfig(config.NamespaceName, kubeaccess.ConfigID).Metadata())
|
||||
if err != nil {
|
||||
if !state.IsNotFoundError(err) {
|
||||
return fmt.Errorf("error fetching kubeaccess config: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var kubeaccessConfigSpec *kubeaccess.ConfigSpec
|
||||
|
||||
if kubeaccessConfig != nil {
|
||||
kubeaccessConfigSpec = kubeaccessConfig.TypedSpec()
|
||||
}
|
||||
|
||||
if kubeaccessConfig == nil || kubeaccessConfigSpec == nil || !kubeaccessConfigSpec.Enabled {
|
||||
stopCRDController()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
kubeSecretsResources, err := safe.ReaderGet[*secrets.Kubernetes](ctx, r, resource.NewMetadata(
|
||||
secrets.NamespaceName,
|
||||
secrets.KubernetesType,
|
||||
secrets.KubernetesID,
|
||||
resource.VersionUndefined,
|
||||
))
|
||||
if err != nil {
|
||||
if !state.IsNotFoundError(err) {
|
||||
return fmt.Errorf("error fetching kubernetes secrets: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
kubeSecretsSpec := kubeSecretsResources.TypedSpec()
|
||||
|
||||
osSecretsResource, err := safe.ReaderGet[*secrets.OSRoot](ctx, r, resource.NewMetadata(
|
||||
secrets.NamespaceName,
|
||||
secrets.OSRootType,
|
||||
secrets.OSRootID,
|
||||
resource.VersionUndefined,
|
||||
))
|
||||
if err != nil {
|
||||
if !state.IsNotFoundError(err) {
|
||||
return fmt.Errorf("error fetching os secrets: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
osSecretsSpec := osSecretsResource.TypedSpec()
|
||||
|
||||
kubeconfig, err := clientcmd.BuildConfigFromKubeconfigGetter("", func() (*clientcmdapi.Config, error) {
|
||||
return clientcmd.Load([]byte(kubeSecretsSpec.LocalhostAdminKubeconfig))
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading kubeconfig: %w", err)
|
||||
}
|
||||
|
||||
stopCRDController()
|
||||
|
||||
var crdControllerCtx context.Context
|
||||
|
||||
crdControllerCtx, crdControllerCtxCancel = context.WithCancel(ctx) //nolint:govet
|
||||
|
||||
go func() {
|
||||
crdControllerErrCh <- ctrl.runCRDController(
|
||||
crdControllerCtx,
|
||||
osSecretsSpec.CA,
|
||||
kubeconfig,
|
||||
kubeaccessConfigSpec,
|
||||
logger,
|
||||
)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *CRDController) runCRDController(
|
||||
ctx context.Context,
|
||||
talosCA *x509.PEMEncodedCertificateAndKey,
|
||||
kubeconfig *rest.Config,
|
||||
kubeaccessCfgSpec *kubeaccess.ConfigSpec,
|
||||
logger *zap.Logger,
|
||||
) error {
|
||||
return etcd.WithLock(ctx, constants.EtcdTalosServiceAccountCRDControllerMutex, logger, func() error {
|
||||
crdCtrl, err := serviceaccount.NewCRDController(
|
||||
talosCA,
|
||||
kubeconfig,
|
||||
kubeaccessCfgSpec.AllowedKubernetesNamespaces,
|
||||
kubeaccessCfgSpec.AllowedAPIRoles,
|
||||
logger,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return crdCtrl.Run(ctx, 1)
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,644 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package serviceaccount
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
stdlibx509 "crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/talos-systems/crypto/x509"
|
||||
"go.uber.org/zap"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kubeerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/dynamic/dynamiclister"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/connrotation"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
|
||||
taloskubernetes "github.com/talos-systems/talos/pkg/kubernetes"
|
||||
clientconfig "github.com/talos-systems/talos/pkg/machinery/client/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/generate"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/machinery/generic/slices"
|
||||
"github.com/talos-systems/talos/pkg/machinery/role"
|
||||
)
|
||||
|
||||
const (
|
||||
certTTL = time.Hour * 6
|
||||
certRenewThreshold = time.Hour * 1
|
||||
|
||||
successResourceSynced = "Synced"
|
||||
messageResourceSynced = "Synced successfully"
|
||||
|
||||
errResourceExists = "ErrResourceExists"
|
||||
messageResourceExists = "%s already exists and is not managed by controller: %s"
|
||||
|
||||
errRolesNotFound = "ErrRolesNotFound"
|
||||
messageRolesNotFound = "Roles not found"
|
||||
|
||||
errNamespaceNotAllowed = "ErrNamespaceNotAllowed"
|
||||
messageNamespaceNotAllowed = "Namespace is not allowed: %s"
|
||||
|
||||
errRolesNotAllowed = "ErrRolesNotAllowed"
|
||||
messageRolesNotAllowed = "Roles not allowed: %v"
|
||||
|
||||
controllerAgentName = "talos-sa-controller"
|
||||
informerResyncPeriod = time.Minute * 1
|
||||
|
||||
talosconfigContextName = "default"
|
||||
endpoint = constants.KubernetesTalosAPIServiceName + "." + constants.KubernetesTalosAPIServiceNamespace
|
||||
|
||||
kindSecret = "Secret"
|
||||
)
|
||||
|
||||
var (
|
||||
talosSAGV = schema.GroupVersion{
|
||||
Group: constants.ServiceAccountResourceGroup,
|
||||
Version: constants.ServiceAccountResourceVersion,
|
||||
}
|
||||
|
||||
talosSAGVR = talosSAGV.WithResource(constants.ServiceAccountResourcePlural)
|
||||
talosSAGVK = talosSAGV.WithKind(constants.ServiceAccountResourceKind)
|
||||
)
|
||||
|
||||
// CRDController is the controller implementation for TalosServiceAccount resources.
|
||||
type CRDController struct {
|
||||
talosCA *x509.PEMEncodedCertificateAndKey
|
||||
|
||||
allowedNamespaces []string
|
||||
allowedRoles map[string]struct{}
|
||||
|
||||
queue workqueue.RateLimitingInterface
|
||||
|
||||
kubeInformerFactory kubeinformers.SharedInformerFactory
|
||||
dynamicInformerFactory dynamicinformer.DynamicSharedInformerFactory
|
||||
|
||||
kubeClient kubernetes.Interface
|
||||
dynamicClient dynamic.Interface
|
||||
dialer *connrotation.Dialer
|
||||
|
||||
secretsSynced cache.InformerSynced
|
||||
talosSAsSynced cache.InformerSynced
|
||||
|
||||
secretsLister corelisters.SecretLister
|
||||
dynamicLister dynamiclister.Lister
|
||||
|
||||
eventRecorder record.EventRecorder
|
||||
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCRDController creates a new CRD controller.
|
||||
func NewCRDController(
|
||||
talosCA *x509.PEMEncodedCertificateAndKey,
|
||||
kubeconfig *rest.Config,
|
||||
allowedNamespaces []string,
|
||||
allowedRoles []string,
|
||||
logger *zap.Logger,
|
||||
) (*CRDController, error) {
|
||||
dialer := taloskubernetes.NewDialer()
|
||||
kubeconfig.Dial = dialer.DialContext
|
||||
|
||||
kubeCli, err := kubernetes.NewForConfig(kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dynCli, err := dynamic.NewForConfig(kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dynamicInformerFactory := dynamicinformer.NewDynamicSharedInformerFactory(dynCli, informerResyncPeriod)
|
||||
resourceInformer := dynamicInformerFactory.ForResource(talosSAGVR)
|
||||
informer := resourceInformer.Informer()
|
||||
|
||||
indexer := informer.GetIndexer()
|
||||
lister := dynamiclister.New(indexer, talosSAGVR)
|
||||
|
||||
kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeCli, informerResyncPeriod)
|
||||
secrets := kubeInformerFactory.Core().V1().Secrets()
|
||||
|
||||
logger.Debug("creating event broadcaster")
|
||||
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartStructuredLogging(0)
|
||||
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeCli.CoreV1().Events("")})
|
||||
|
||||
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})
|
||||
|
||||
controller := CRDController{
|
||||
talosCA: talosCA,
|
||||
allowedNamespaces: allowedNamespaces,
|
||||
allowedRoles: slices.ToSet(allowedRoles),
|
||||
dynamicInformerFactory: dynamicInformerFactory,
|
||||
kubeInformerFactory: kubeInformerFactory,
|
||||
kubeClient: kubeCli,
|
||||
dynamicClient: dynCli,
|
||||
dialer: dialer,
|
||||
dynamicLister: lister,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(
|
||||
workqueue.DefaultControllerRateLimiter(),
|
||||
constants.ServiceAccountResourceKind,
|
||||
),
|
||||
logger: logger,
|
||||
secretsSynced: secrets.Informer().HasSynced,
|
||||
talosSAsSynced: informer.HasSynced,
|
||||
eventRecorder: recorder,
|
||||
secretsLister: secrets.Lister(),
|
||||
}
|
||||
|
||||
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: controller.enqueueTalosSA,
|
||||
UpdateFunc: func(oldTalosSA, newTalosSA interface{}) {
|
||||
controller.enqueueTalosSA(newTalosSA)
|
||||
},
|
||||
})
|
||||
|
||||
secrets.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: controller.handleSecret,
|
||||
UpdateFunc: func(oldSec, newSec interface{}) {
|
||||
newSecret := newSec.(*corev1.Secret) //nolint:errcheck
|
||||
oldSecret := oldSec.(*corev1.Secret) //nolint:errcheck
|
||||
|
||||
if newSecret.ResourceVersion == oldSecret.ResourceVersion {
|
||||
return
|
||||
}
|
||||
|
||||
controller.handleSecret(newSec)
|
||||
},
|
||||
DeleteFunc: controller.handleSecret,
|
||||
})
|
||||
|
||||
return &controller, nil
|
||||
}
|
||||
|
||||
// Run starts the CRD controller.
|
||||
func (t *CRDController) Run(ctx context.Context, workers int) error {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
defer func() {
|
||||
t.queue.ShutDown()
|
||||
t.dialer.CloseAll()
|
||||
|
||||
wg.Wait()
|
||||
t.logger.Debug("all workers have shut down")
|
||||
}()
|
||||
|
||||
t.kubeInformerFactory.Start(ctx.Done())
|
||||
t.dynamicInformerFactory.Start(ctx.Done())
|
||||
|
||||
t.logger.Sugar().Debugf("starting %s controller", constants.ServiceAccountResourceKind)
|
||||
|
||||
t.logger.Debug("waiting for informer caches to sync")
|
||||
|
||||
if ok := cache.WaitForCacheSync(ctx.Done(), t.secretsSynced, t.talosSAsSynced); !ok {
|
||||
return fmt.Errorf("failed to wait for caches to sync")
|
||||
}
|
||||
|
||||
t.logger.Debug("starting workers")
|
||||
|
||||
wg.Add(workers)
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
wait.Until(func() { t.runWorker(ctx) }, time.Second, ctx.Done())
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
t.logger.Debug("started workers")
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
t.logger.Debug("shutting down workers")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CRDController) runWorker(ctx context.Context) {
|
||||
for t.processNextWorkItem(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *CRDController) processNextWorkItem(ctx context.Context) bool {
|
||||
obj, shutdown := t.queue.Get()
|
||||
|
||||
if shutdown {
|
||||
return false
|
||||
}
|
||||
|
||||
err := func(obj interface{}) error {
|
||||
defer t.queue.Done(obj)
|
||||
|
||||
var key string
|
||||
|
||||
var ok bool
|
||||
|
||||
if key, ok = obj.(string); !ok {
|
||||
t.queue.Forget(obj)
|
||||
utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := t.syncHandler(ctx, key); err != nil {
|
||||
t.queue.AddRateLimited(key)
|
||||
|
||||
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
|
||||
}
|
||||
|
||||
t.queue.Forget(obj)
|
||||
t.logger.Sugar().Debugf("successfully synced '%s'", key)
|
||||
|
||||
return nil
|
||||
}(obj)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
//nolint:gocyclo,cyclop,dupl
|
||||
func (t *CRDController) syncHandler(ctx context.Context, key string) error {
|
||||
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
talosSA, err := t.dynamicLister.Namespace(namespace).Get(name)
|
||||
if err != nil {
|
||||
if kubeerrors.IsNotFound(err) {
|
||||
utilruntime.HandleError(fmt.Errorf("talosSA '%s' in work queue no longer exists", key))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
secret, err := t.secretsLister.Secrets(namespace).Get(name)
|
||||
secretNotFound := kubeerrors.IsNotFound(err)
|
||||
|
||||
if err != nil && !secretNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if !secretNotFound && !metav1.IsControlledBy(secret, talosSA) {
|
||||
msg := fmt.Sprintf(messageResourceExists, kindSecret, key)
|
||||
|
||||
err = t.updateTalosSAStatus(ctx, talosSA, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.eventRecorder.Event(talosSA, corev1.EventTypeWarning, errResourceExists, msg)
|
||||
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
desiredRoles, found, err := unstructured.NestedStringSlice(talosSA.UnstructuredContent(), "spec", "roles")
|
||||
if err != nil || !found {
|
||||
msg := fmt.Sprint(messageRolesNotFound)
|
||||
|
||||
updateErr := t.updateTalosSAStatus(ctx, talosSA, msg)
|
||||
if updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
|
||||
t.eventRecorder.Event(talosSA, corev1.EventTypeWarning, errRolesNotFound, messageRolesNotFound)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", msg, err)
|
||||
}
|
||||
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
desiredRoleSet, _ := role.Parse(desiredRoles)
|
||||
|
||||
if !slices.Contains(t.allowedNamespaces, func(allowedNS string) bool {
|
||||
return allowedNS == namespace
|
||||
}) {
|
||||
msg := fmt.Sprintf(messageNamespaceNotAllowed, namespace)
|
||||
|
||||
err = t.updateTalosSAStatus(ctx, talosSA, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.eventRecorder.Event(talosSA, corev1.EventTypeWarning, errNamespaceNotAllowed, msg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var unallowedRoles []string
|
||||
|
||||
for _, desiredRole := range desiredRoles {
|
||||
_, allowed := t.allowedRoles[desiredRole]
|
||||
if !allowed {
|
||||
unallowedRoles = append(unallowedRoles, desiredRole)
|
||||
}
|
||||
}
|
||||
|
||||
if len(unallowedRoles) > 0 {
|
||||
msg := fmt.Sprintf(messageRolesNotAllowed, unallowedRoles)
|
||||
|
||||
err = t.updateTalosSAStatus(ctx, talosSA, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.eventRecorder.Event(talosSA, corev1.EventTypeWarning, errRolesNotAllowed, msg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if secretNotFound {
|
||||
var newSecret *corev1.Secret
|
||||
|
||||
newSecret, err = t.newSecret(talosSA, desiredRoleSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = t.kubeClient.CoreV1().Secrets(namespace).Create(ctx, newSecret, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if t.needsUpdate(secret, desiredRoleSet.Strings()) {
|
||||
var newTalosconfigBytes []byte
|
||||
|
||||
newTalosconfigBytes, err = t.generateTalosconfig(desiredRoleSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret.Data[constants.TalosconfigFilename] = newTalosconfigBytes
|
||||
|
||||
_, err = t.kubeClient.CoreV1().Secrets(namespace).Update(ctx, secret, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = t.updateTalosSAStatus(ctx, talosSA, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.eventRecorder.Event(talosSA, corev1.EventTypeNormal, successResourceSynced, messageResourceSynced)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CRDController) enqueueTalosSA(obj interface{}) {
|
||||
key, err := cache.MetaNamespaceKeyFunc(obj)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.queue.Add(key)
|
||||
}
|
||||
|
||||
func (t *CRDController) handleSecret(obj interface{}) {
|
||||
var object metav1.Object
|
||||
|
||||
var ok bool
|
||||
|
||||
if object, ok = obj.(metav1.Object); !ok {
|
||||
tombstone, tombstoneOK := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !tombstoneOK {
|
||||
utilruntime.HandleError(fmt.Errorf("error decoding object, invalid type"))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
object, tombstoneOK = tombstone.Obj.(metav1.Object)
|
||||
if !tombstoneOK {
|
||||
utilruntime.HandleError(fmt.Errorf("error decoding object tombstone, invalid type"))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.logger.Sugar().Debugf("recovered deleted object '%s' from tombstone", object.GetName())
|
||||
}
|
||||
|
||||
t.logger.Sugar().Debugf("processing object: %s", object.GetName())
|
||||
|
||||
if ownerRef := metav1.GetControllerOf(object); ownerRef != nil {
|
||||
if ownerRef.Kind != constants.ServiceAccountResourceKind {
|
||||
return
|
||||
}
|
||||
|
||||
talosSA, err := t.dynamicLister.Namespace(object.GetNamespace()).Get(ownerRef.Name)
|
||||
if err != nil {
|
||||
t.logger.Sugar().Debugf("ignoring orphaned object '%s/%s' of %s '%s'",
|
||||
object.GetNamespace(), object.GetName(), constants.ServiceAccountResourceKind, ownerRef.Name)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.enqueueTalosSA(talosSA)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (t *CRDController) updateTalosSAStatus(
|
||||
ctx context.Context,
|
||||
talosSA *unstructured.Unstructured,
|
||||
failureReason string,
|
||||
) error {
|
||||
var err error
|
||||
|
||||
talosSACopy := talosSA.DeepCopy()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if failureReason == "" {
|
||||
unstructured.RemoveNestedField(talosSACopy.UnstructuredContent(), "status", "failureReason")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = unstructured.SetNestedField(talosSACopy.UnstructuredContent(), failureReason, "status", "failureReason")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = t.dynamicClient.Resource(talosSAGVR).
|
||||
Namespace(talosSACopy.GetNamespace()).
|
||||
Update(ctx, talosSACopy, metav1.UpdateOptions{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (t *CRDController) needsUpdate(secret *corev1.Secret, desiredRoles []string) bool {
|
||||
talosconfigInSecret, ok := secret.Data[constants.TalosconfigFilename]
|
||||
if !ok {
|
||||
t.logger.Debug("talosconfig not found in secret", zap.String("key", constants.TalosconfigFilename))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
parsedTalosconfigInSecret, err := clientconfig.ReadFrom(bytes.NewReader(talosconfigInSecret))
|
||||
if err != nil {
|
||||
t.logger.Debug("error parsing talosconfig in secret", zap.Error(err))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
talosconfigCtx := parsedTalosconfigInSecret.Contexts[parsedTalosconfigInSecret.Context]
|
||||
|
||||
talosconfigCA, err := base64.StdEncoding.DecodeString(talosconfigCtx.CA)
|
||||
if err != nil {
|
||||
t.logger.Debug("error decoding talosconfig CA", zap.Error(err))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(t.talosCA.Crt, talosconfigCA) {
|
||||
t.logger.Debug("ca mismatch detected")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if len(talosconfigCtx.Endpoints) != 1 || talosconfigCtx.Endpoints[0] != endpoint {
|
||||
t.logger.Debug(
|
||||
"endpoint mismatch detected",
|
||||
zap.Strings("actual", talosconfigCtx.Endpoints),
|
||||
zap.Strings("expected", []string{endpoint}),
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
talosconfigCRT, err := base64.StdEncoding.DecodeString(talosconfigCtx.Crt)
|
||||
if err != nil {
|
||||
t.logger.Debug("error decoding talosconfig CRT", zap.Error(err))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(talosconfigCRT)
|
||||
if block == nil {
|
||||
t.logger.Debug("could not decode talosconfig CRT")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
certificate, err := stdlibx509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
t.logger.Debug("error parsing certificate in talosconfig of secret", zap.Error(err))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if certificate.NotAfter.IsZero() {
|
||||
t.logger.Debug("certificate in talosconfig of secret has no expiration date", zap.Error(err))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if time.Now().Add(certTTL).Before(certificate.NotAfter) {
|
||||
t.logger.Debug(
|
||||
"certificate in talosconfig has expiration date too far in the future",
|
||||
zap.Time("expiration", certificate.NotAfter),
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if time.Now().Add(certRenewThreshold).After(certificate.NotAfter) {
|
||||
t.logger.Debug(
|
||||
"certificate in talosconfig needs renewal",
|
||||
zap.Time("expiration", certificate.NotAfter),
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
actualRoles := certificate.Subject.Organization
|
||||
|
||||
sort.Strings(actualRoles)
|
||||
sort.Strings(desiredRoles)
|
||||
|
||||
if !reflect.DeepEqual(actualRoles, desiredRoles) {
|
||||
t.logger.Debug("roles in certificate do not match desired roles",
|
||||
zap.Strings("actual", actualRoles), zap.Strings("desired", desiredRoles))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *CRDController) newSecret(talosSA *unstructured.Unstructured, roles role.Set) (*corev1.Secret, error) {
|
||||
config, err := t.generateTalosconfig(roles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: talosSA.GetName(),
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
*metav1.NewControllerRef(talosSA, talosSAGVK),
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
constants.TalosconfigFilename: config,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *CRDController) generateTalosconfig(roles role.Set) ([]byte, error) {
|
||||
var newCert *x509.PEMEncodedCertificateAndKey
|
||||
|
||||
newCert, err := generate.NewAdminCertificateAndKey(time.Now(), t.talosCA, roles, certTTL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newTalosconfig := clientconfig.NewConfig(talosconfigContextName, []string{endpoint}, t.talosCA.Crt, newCert)
|
||||
|
||||
return newTalosconfig.Bytes()
|
||||
}
|
||||
@ -145,6 +145,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
|
||||
&k8s.StaticPodConfigController{},
|
||||
&kubeaccess.ConfigController{},
|
||||
&kubeaccess.EndpointController{},
|
||||
&kubeaccess.CRDController{},
|
||||
&kubespan.ConfigController{},
|
||||
&kubespan.EndpointController{},
|
||||
&kubespan.IdentityController{},
|
||||
|
||||
303
internal/integration/api/serviceaccount.go
Normal file
303
internal/integration/api/serviceaccount.go
Normal file
@ -0,0 +1,303 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//go:build integration_api
|
||||
// +build integration_api
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/siderolabs/go-pointer"
|
||||
"github.com/talos-systems/go-retry/retry"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
eventsv1 "k8s.io/api/events/v1"
|
||||
kubeerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/talos-systems/talos/internal/integration/base"
|
||||
machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine"
|
||||
"github.com/talos-systems/talos/pkg/machinery/client"
|
||||
"github.com/talos-systems/talos/pkg/machinery/client/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
var (
|
||||
serviceAccountGVR = schema.GroupVersionResource{
|
||||
Group: constants.ServiceAccountResourceGroup,
|
||||
Version: constants.ServiceAccountResourceVersion,
|
||||
Resource: constants.ServiceAccountResourcePlural,
|
||||
}
|
||||
secretGVR = schema.GroupVersionResource{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Resource: "secrets",
|
||||
}
|
||||
)
|
||||
|
||||
// ServiceAccountSuite verifies Talos ServiceAccount.
|
||||
type ServiceAccountSuite struct {
|
||||
base.K8sSuite
|
||||
|
||||
ctx context.Context //nolint:containedctx
|
||||
ctxCancel context.CancelFunc
|
||||
}
|
||||
|
||||
// SuiteName ...
|
||||
func (suite *ServiceAccountSuite) SuiteName() string {
|
||||
return "api.ServiceAccountSuite"
|
||||
}
|
||||
|
||||
// SetupTest ...
|
||||
func (suite *ServiceAccountSuite) SetupTest() {
|
||||
// make sure API calls have timeout
|
||||
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
}
|
||||
|
||||
// TearDownTest ...
|
||||
func (suite *ServiceAccountSuite) TearDownTest() {
|
||||
if suite.ctxCancel != nil {
|
||||
suite.ctxCancel()
|
||||
}
|
||||
}
|
||||
|
||||
// TestValid tests Kubernetes service accounts.
|
||||
//
|
||||
//nolint:dupl
|
||||
func (suite *ServiceAccountSuite) TestValid() {
|
||||
name := "test-valid"
|
||||
|
||||
err := suite.configureAPIAccess(true, []string{"os:reader"}, []string{"kube-system"})
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
_, err = suite.getCRD()
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
sa, err := suite.createServiceAccount("kube-system", name, []string{"os:reader"})
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
defer suite.DeleteResource(suite.ctx, serviceAccountGVR, "default", name) // nolint:errcheck
|
||||
|
||||
err = suite.WaitForEventExists(suite.ctx, "kube-system", func(event eventsv1.Event) bool {
|
||||
return event.Regarding.UID == sa.GetUID() &&
|
||||
event.Type == corev1.EventTypeNormal &&
|
||||
event.Reason == "Synced"
|
||||
})
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
secret, err := suite.waitForSecret("kube-system", name)
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
talosConfig := secret.Data["config"]
|
||||
|
||||
conf, err := config.FromBytes(talosConfig)
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
expectedServiceName := fmt.Sprintf(
|
||||
"%s.%s",
|
||||
constants.KubernetesTalosAPIServiceName,
|
||||
constants.KubernetesTalosAPIServiceNamespace,
|
||||
)
|
||||
suite.Assert().Equal([]string{expectedServiceName}, conf.Contexts[conf.Context].Endpoints)
|
||||
|
||||
err = suite.DeleteResource(suite.ctx, serviceAccountGVR, "kube-system", name)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
err = suite.EnsureResourceIsDeleted(suite.ctx, 30*time.Second, secretGVR, "kube-system", name)
|
||||
suite.Assert().NoError(err)
|
||||
}
|
||||
|
||||
// TestNotAllowedNamespace tests Kubernetes service accounts in not allowed namespaces.
|
||||
//
|
||||
//nolint:dupl
|
||||
func (suite *ServiceAccountSuite) TestNotAllowedNamespace() {
|
||||
name := "test-allowed-ns"
|
||||
|
||||
err := suite.configureAPIAccess(true, []string{"os:reader"}, []string{"kube-system"})
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
sa, err := suite.createServiceAccount("default", name, []string{"os:reader"})
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
defer suite.DeleteResource(suite.ctx, serviceAccountGVR, "default", name) // nolint:errcheck
|
||||
|
||||
err = suite.WaitForEventExists(suite.ctx, "default", func(event eventsv1.Event) bool {
|
||||
return event.Regarding.UID == sa.GetUID() &&
|
||||
event.Type == corev1.EventTypeWarning &&
|
||||
event.Reason == "ErrNamespaceNotAllowed"
|
||||
})
|
||||
suite.Assert().NoError(err)
|
||||
}
|
||||
|
||||
// TestNotAllowedRoles tests Kubernetes service accounts with not allowed roles.
|
||||
//
|
||||
//nolint:dupl
|
||||
func (suite *ServiceAccountSuite) TestNotAllowedRoles() {
|
||||
name := "test-not-allowed-roles"
|
||||
|
||||
err := suite.configureAPIAccess(true, []string{"os:reader"}, []string{"kube-system"})
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
sa, err := suite.createServiceAccount("kube-system", name, []string{"os:admin"})
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
defer suite.DeleteResource(suite.ctx, serviceAccountGVR, "kube-system", name) // nolint:errcheck
|
||||
|
||||
err = suite.WaitForEventExists(suite.ctx, "kube-system", func(event eventsv1.Event) bool {
|
||||
return event.Regarding.UID == sa.GetUID() &&
|
||||
event.Type == corev1.EventTypeWarning &&
|
||||
event.Reason == "ErrRolesNotAllowed"
|
||||
})
|
||||
suite.Assert().NoError(err)
|
||||
}
|
||||
|
||||
// TestFeatureNotEnabled tests Kubernetes service accounts when API access feature is not enabled.
|
||||
//
|
||||
//nolint:dupl
|
||||
func (suite *ServiceAccountSuite) TestFeatureNotEnabled() {
|
||||
name := "test-feature-not-enabled"
|
||||
|
||||
err := suite.configureAPIAccess(false, []string{"os:reader"}, []string{"kube-system"})
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
sa, err := suite.createServiceAccount("kube-system", name, []string{"os:reader"})
|
||||
if kubeerrors.IsNotFound(err) {
|
||||
// CRD is not created because the feature was never enabled, all good
|
||||
return
|
||||
}
|
||||
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
defer suite.DeleteResource(suite.ctx, serviceAccountGVR, "kube-system", name) // nolint:errcheck
|
||||
|
||||
err = suite.WaitForEventExists(suite.ctx, "kube-system", func(event eventsv1.Event) bool {
|
||||
return event.Regarding.UID == sa.GetUID() &&
|
||||
event.Type == corev1.EventTypeWarning &&
|
||||
event.Reason == "ErrAccessNotEnabled"
|
||||
})
|
||||
|
||||
suite.Assert().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ServiceAccountSuite) waitForSecret(ns, name string) (*corev1.Secret, error) {
|
||||
var (
|
||||
secret *corev1.Secret
|
||||
err error
|
||||
)
|
||||
|
||||
err = retry.Constant(1*time.Minute).RetryWithContext(suite.ctx, func(ctx context.Context) error {
|
||||
secret, err = suite.Clientset.CoreV1().Secrets(ns).Get(suite.ctx, name, metav1.GetOptions{})
|
||||
if kubeerrors.IsNotFound(err) {
|
||||
return retry.ExpectedError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (suite *ServiceAccountSuite) getCRD() (*unstructured.Unstructured, error) {
|
||||
crdName := fmt.Sprintf("%s.%s", constants.ServiceAccountResourcePlural, constants.ServiceAccountResourceGroup)
|
||||
|
||||
return suite.DynamicClient.Resource(schema.GroupVersionResource{
|
||||
Group: "apiextensions.k8s.io",
|
||||
Version: "v1",
|
||||
Resource: "customresourcedefinitions",
|
||||
}).Get(suite.ctx, crdName, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
func (suite *ServiceAccountSuite) createServiceAccount(ns string, name string, roles []string) (*unstructured.Unstructured, error) {
|
||||
return suite.DynamicClient.Resource(serviceAccountGVR).Namespace(ns).Create(suite.ctx, &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": fmt.Sprintf("%s/%s", constants.ServiceAccountResourceGroup, constants.ServiceAccountResourceVersion),
|
||||
"kind": constants.ServiceAccountResourceKind,
|
||||
"metadata": map[string]interface{}{
|
||||
"name": name,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"roles": roles,
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
// configureAPIAccess configures the API access feature on all control plane nodes.
|
||||
func (suite *ServiceAccountSuite) configureAPIAccess(
|
||||
enabled bool,
|
||||
allowedRoles []string,
|
||||
allowedNamespaces []string,
|
||||
) error {
|
||||
controlPlaneIPs := suite.DiscoverNodeInternalIPsByType(suite.ctx, machine.TypeControlPlane)
|
||||
|
||||
for _, ip := range controlPlaneIPs {
|
||||
nodeCtx := client.WithNodes(suite.ctx, ip)
|
||||
|
||||
nodeConfig, err := suite.ReadConfigFromNode(nodeCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodeConfigRaw, ok := nodeConfig.Raw().(*v1alpha1.Config)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected node config type %T", nodeConfig.Raw())
|
||||
}
|
||||
|
||||
accessConfig := v1alpha1.KubernetesTalosAPIAccessConfig{
|
||||
AccessEnabled: pointer.To(enabled),
|
||||
AccessAllowedRoles: allowedRoles,
|
||||
AccessAllowedKubernetesNamespaces: allowedNamespaces,
|
||||
}
|
||||
|
||||
nodeConfigRaw.MachineConfig.MachineFeatures.KubernetesTalosAPIAccessConfig = &accessConfig
|
||||
|
||||
bytes, err := nodeConfigRaw.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = suite.Client.ApplyConfiguration(nodeCtx, &machineapi.ApplyConfigurationRequest{
|
||||
Data: bytes,
|
||||
Mode: machineapi.ApplyConfigurationRequest_NO_REBOOT,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if enabled { // wait for CRD and the Talos endpoint to be created
|
||||
return retry.Constant(30*time.Second).RetryWithContext(suite.ctx, func(ctx context.Context) error {
|
||||
_, err := suite.getCRD()
|
||||
if err != nil {
|
||||
return retry.ExpectedError(err)
|
||||
}
|
||||
|
||||
_, err = suite.Clientset.CoreV1().
|
||||
Services(constants.KubernetesTalosAPIServiceNamespace).
|
||||
Get(suite.ctx, constants.KubernetesTalosAPIServiceName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return retry.ExpectedError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
allSuites = append(allSuites, new(ServiceAccountSuite))
|
||||
}
|
||||
@ -14,12 +14,17 @@ import (
|
||||
|
||||
"github.com/talos-systems/go-retry/retry"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
eventsv1 "k8s.io/api/events/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/generic/slices"
|
||||
)
|
||||
|
||||
// K8sSuite is a base suite for K8s tests.
|
||||
@ -27,6 +32,7 @@ type K8sSuite struct {
|
||||
APISuite
|
||||
|
||||
Clientset *kubernetes.Clientset
|
||||
DynamicClient dynamic.Interface
|
||||
DiscoveryClient *discovery.DiscoveryClient
|
||||
}
|
||||
|
||||
@ -51,6 +57,9 @@ func (k8sSuite *K8sSuite) SetupSuite() {
|
||||
k8sSuite.Clientset, err = kubernetes.NewForConfig(config)
|
||||
k8sSuite.Require().NoError(err)
|
||||
|
||||
k8sSuite.DynamicClient, err = dynamic.NewForConfig(config)
|
||||
k8sSuite.Require().NoError(err)
|
||||
|
||||
k8sSuite.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(config)
|
||||
k8sSuite.Require().NoError(err)
|
||||
}
|
||||
@ -111,3 +120,49 @@ func (k8sSuite *K8sSuite) GetK8sNodeReadinessStatus(ctx context.Context, nodeNam
|
||||
|
||||
return "", fmt.Errorf("node %s has no readiness condition", nodeName)
|
||||
}
|
||||
|
||||
// DeleteResource deletes the resource with the given GroupVersionResource, namespace and name.
|
||||
// Does not return an error if the resource is not found.
|
||||
func (k8sSuite *K8sSuite) DeleteResource(ctx context.Context, gvr schema.GroupVersionResource, ns, name string) error {
|
||||
err := k8sSuite.DynamicClient.Resource(gvr).Namespace(ns).Delete(ctx, name, metav1.DeleteOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// EnsureResourceIsDeleted ensures that the resource with the given GroupVersionResource, namespace and name does not exist on Kubernetes.
|
||||
// It repeatedly checks the resource for the given duration.
|
||||
func (k8sSuite *K8sSuite) EnsureResourceIsDeleted(
|
||||
ctx context.Context,
|
||||
duration time.Duration,
|
||||
gvr schema.GroupVersionResource,
|
||||
ns, name string,
|
||||
) error {
|
||||
return retry.Constant(duration).RetryWithContext(ctx, func(ctx context.Context) error {
|
||||
_, err := k8sSuite.DynamicClient.Resource(gvr).Namespace(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForEventExists waits for the event with the given namespace and check condition to exist on Kubernetes.
|
||||
func (k8sSuite *K8sSuite) WaitForEventExists(ctx context.Context, ns string, checkFn func(event eventsv1.Event) bool) error {
|
||||
return retry.Constant(15*time.Second).RetryWithContext(ctx, func(ctx context.Context) error {
|
||||
events, err := k8sSuite.Clientset.EventsV1().Events(ns).List(ctx, metav1.ListOptions{})
|
||||
|
||||
filteredEvents := slices.Filter(events.Items, func(item eventsv1.Event) bool {
|
||||
return checkFn(item)
|
||||
})
|
||||
|
||||
if len(filteredEvents) == 0 {
|
||||
return retry.ExpectedError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
95
internal/integration/cli/inject.go
Normal file
95
internal/integration/cli/inject.go
Normal file
@ -0,0 +1,95 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//go:build integration_cli
|
||||
// +build integration_cli
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/integration/base"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed testdata/inject/talosconfig-input.yaml
|
||||
inputManifests []byte
|
||||
|
||||
//go:embed testdata/inject/talosconfig-expected.yaml
|
||||
expectedManifests []byte
|
||||
)
|
||||
|
||||
// InjectSuite verifies inject command.
|
||||
type InjectSuite struct {
|
||||
base.CLISuite
|
||||
}
|
||||
|
||||
// SuiteName ...
|
||||
func (suite *InjectSuite) SuiteName() string {
|
||||
return "cli.InjectSuite"
|
||||
}
|
||||
|
||||
// TestServiceAccount tests inject serviceaccount command.
|
||||
func (suite *InjectSuite) TestServiceAccount() {
|
||||
suite.testServiceAccount(inputManifests)
|
||||
}
|
||||
|
||||
// TestServiceAccountAlreadyInjectedNoChange tests inject serviceaccount command when the input manifest is already injected,
|
||||
// makes sure that it stays the same.
|
||||
func (suite *InjectSuite) TestServiceAccountAlreadyInjectedNoChange() {
|
||||
suite.testServiceAccount(expectedManifests)
|
||||
}
|
||||
|
||||
func (suite *InjectSuite) testServiceAccount(input []byte) {
|
||||
expectedDocs, err := yamlDocs(expectedManifests)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
tempDir := suite.T().TempDir()
|
||||
|
||||
inputPath := filepath.Join(tempDir, "input.yaml")
|
||||
|
||||
err = os.WriteFile(inputPath, input, 0o644)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
stdout, _ := suite.RunCLI([]string{"inject", "serviceaccount", "-f", inputPath, "--roles", "os:reader,os:admin"})
|
||||
|
||||
stdoutDocs, err := yamlDocs([]byte(stdout))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().Equal(expectedDocs, stdoutDocs, "inject serviceaccount output did not match expected output")
|
||||
}
|
||||
|
||||
func yamlDocs(input []byte) ([]map[string]any, error) {
|
||||
decoder := yaml.NewDecoder(bytes.NewReader(input))
|
||||
|
||||
var docs []map[string]any
|
||||
|
||||
for {
|
||||
var doc map[string]any
|
||||
|
||||
if err := decoder.Decode(&doc); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("document decode failed: %w", err)
|
||||
}
|
||||
|
||||
docs = append(docs, doc)
|
||||
}
|
||||
|
||||
return docs, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
allSuites = append(allSuites, new(InjectSuite))
|
||||
}
|
||||
302
internal/integration/cli/testdata/inject/talosconfig-expected.yaml
vendored
Normal file
302
internal/integration/cli/testdata/inject/talosconfig-expected.yaml
vendored
Normal file
@ -0,0 +1,302 @@
|
||||
apiVersion: talos.dev/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: donottouch
|
||||
spec:
|
||||
roles:
|
||||
- os:reader
|
||||
- os:admin
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: test1
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: TEST
|
||||
value: test
|
||||
image: alpine:3
|
||||
name: container1
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /mnt/vol1
|
||||
name: vol1
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
- image: alpine:3
|
||||
name: container2
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
initContainers:
|
||||
- image: busybox
|
||||
name: init1
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/hello
|
||||
name: vol1
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
- image: busybox
|
||||
name: init2
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/hello
|
||||
name: vol1
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: vol1
|
||||
- name: talos-secrets
|
||||
secret:
|
||||
secretName: test1-talos-secrets
|
||||
status: {}
|
||||
---
|
||||
apiVersion: talos.dev/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test1-talos-secrets
|
||||
spec:
|
||||
roles:
|
||||
- os:reader
|
||||
- os:admin
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: test1
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: test
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine:3
|
||||
name: container1
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /mnt/vol1
|
||||
name: vol1
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
- image: alpine:3
|
||||
name: container2
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: vol1
|
||||
- name: talos-secrets
|
||||
secret:
|
||||
secretName: test1-talos-secrets
|
||||
status: {}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: test2
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: test
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine:3
|
||||
name: container1
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /mnt/vol1
|
||||
name: vol1
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
- image: alpine:3
|
||||
name: container2
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: vol1
|
||||
- name: talos-secrets
|
||||
secret:
|
||||
secretName: test2-talos-secrets
|
||||
updateStrategy: {}
|
||||
status:
|
||||
currentNumberScheduled: 0
|
||||
desiredNumberScheduled: 0
|
||||
numberMisscheduled: 0
|
||||
numberReady: 0
|
||||
---
|
||||
apiVersion: talos.dev/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test2-talos-secrets
|
||||
spec:
|
||||
roles:
|
||||
- os:reader
|
||||
- os:admin
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: test3
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: test
|
||||
serviceName: test
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine:3
|
||||
name: container1
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /mnt/vol1
|
||||
name: vol1
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
- image: alpine:3
|
||||
name: container2
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: vol1
|
||||
- name: talos-secrets
|
||||
secret:
|
||||
secretName: test3-talos-secrets
|
||||
updateStrategy: {}
|
||||
status:
|
||||
availableReplicas: 0
|
||||
replicas: 0
|
||||
---
|
||||
apiVersion: talos.dev/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test3-talos-secrets
|
||||
spec:
|
||||
roles:
|
||||
- os:reader
|
||||
- os:admin
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: test4
|
||||
namespace: testns
|
||||
spec:
|
||||
jobTemplate:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine:3
|
||||
name: container1
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /mnt/vol1
|
||||
name: vol1
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
- image: alpine:3
|
||||
name: container2
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
volumes:
|
||||
- name: talos-secrets
|
||||
secret:
|
||||
secretName: test4-talos-secrets
|
||||
schedule: '*/1 * * * *'
|
||||
status: {}
|
||||
---
|
||||
apiVersion: talos.dev/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test4-talos-secrets
|
||||
namespace: testns
|
||||
spec:
|
||||
roles:
|
||||
- os:reader
|
||||
- os:admin
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: test5
|
||||
namespace: testns2
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine:3
|
||||
name: container1
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /mnt/vol1
|
||||
name: vol1
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
- image: alpine:3
|
||||
name: container2
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
volumes:
|
||||
- name: talos-secrets
|
||||
secret:
|
||||
secretName: test5-talos-secrets
|
||||
status: {}
|
||||
---
|
||||
apiVersion: talos.dev/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test5-talos-secrets
|
||||
namespace: testns2
|
||||
spec:
|
||||
roles:
|
||||
- os:reader
|
||||
- os:admin
|
||||
---
|
||||
159
internal/integration/cli/testdata/inject/talosconfig-input.yaml
vendored
Normal file
159
internal/integration/cli/testdata/inject/talosconfig-input.yaml
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
---
|
||||
apiVersion: talos.dev/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: donottouch
|
||||
spec:
|
||||
roles:
|
||||
- os:reader
|
||||
- os:admin
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test1
|
||||
spec:
|
||||
volumes:
|
||||
- name: vol1
|
||||
emptyDir: {}
|
||||
initContainers:
|
||||
- name: init1
|
||||
image: busybox
|
||||
volumeMounts:
|
||||
- name: vol1
|
||||
mountPath: /tmp/hello
|
||||
- name: init2
|
||||
image: busybox
|
||||
volumeMounts:
|
||||
- name: vol1
|
||||
mountPath: /tmp/hello
|
||||
containers:
|
||||
- name: container1
|
||||
image: alpine:3
|
||||
env:
|
||||
- name: TEST
|
||||
value: test
|
||||
volumeMounts:
|
||||
- name: vol1
|
||||
mountPath: /mnt/vol1
|
||||
- name: container2
|
||||
image: alpine:3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test1
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: test
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
volumes:
|
||||
- name: vol1
|
||||
emptyDir: {}
|
||||
containers:
|
||||
- name: container1
|
||||
image: alpine:3
|
||||
volumeMounts:
|
||||
- name: vol1
|
||||
mountPath: /mnt/vol1
|
||||
- name: container2
|
||||
image: alpine:3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: test2
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: test
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
volumes:
|
||||
- name: vol1
|
||||
emptyDir: {}
|
||||
containers:
|
||||
- name: container1
|
||||
image: alpine:3
|
||||
volumeMounts:
|
||||
- name: vol1
|
||||
mountPath: /mnt/vol1
|
||||
- name: container2
|
||||
image: alpine:3
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: test3
|
||||
spec:
|
||||
serviceName: test
|
||||
selector:
|
||||
matchLabels:
|
||||
app: test
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
volumes:
|
||||
- name: vol1
|
||||
emptyDir: {}
|
||||
containers:
|
||||
- name: container1
|
||||
image: alpine:3
|
||||
volumeMounts:
|
||||
- name: vol1
|
||||
mountPath: /mnt/vol1
|
||||
- name: container2
|
||||
image: alpine:3
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: test4
|
||||
namespace: testns
|
||||
spec:
|
||||
schedule: "*/1 * * * *"
|
||||
jobTemplate:
|
||||
metadata:
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: test
|
||||
spec:
|
||||
containers:
|
||||
- name: container1
|
||||
image: alpine:3
|
||||
volumeMounts:
|
||||
- name: vol1
|
||||
mountPath: /mnt/vol1
|
||||
- name: container2
|
||||
image: alpine:3
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: test5
|
||||
namespace: testns2
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: container1
|
||||
image: alpine:3
|
||||
volumeMounts:
|
||||
- name: vol1
|
||||
mountPath: /mnt/vol1
|
||||
- name: container2
|
||||
image: alpine:3
|
||||
@ -11,6 +11,7 @@ package integration_test
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@ -120,7 +121,7 @@ func TestIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
defaultTalosConfig, _ := clientconfig.GetDefaultPath() //nolint:errcheck
|
||||
defaultTalosConfigs, _ := clientconfig.GetDefaultPaths() //nolint:errcheck
|
||||
|
||||
defaultStateDir, err := clientconfig.GetTalosDirectory()
|
||||
if err == nil {
|
||||
@ -130,7 +131,16 @@ func init() {
|
||||
flag.BoolVar(&failFast, "talos.failfast", false, "fail the test run on the first failed test")
|
||||
flag.BoolVar(&crashdumpEnabled, "talos.crashdump", true, "print crashdump on test failure (only if provisioner is enabled)")
|
||||
|
||||
flag.StringVar(&talosConfig, "talos.config", defaultTalosConfig, "The path to the Talos configuration file")
|
||||
flag.StringVar(
|
||||
&talosConfig,
|
||||
"talos.config",
|
||||
defaultTalosConfigs[0].Path,
|
||||
fmt.Sprintf("The path to the Talos configuration file. Defaults to '%s' env variable if set, otherwise '%s' and '%s' in order.",
|
||||
constants.TalosConfigEnvVar,
|
||||
filepath.Join("$HOME", constants.TalosDir, constants.TalosconfigFilename),
|
||||
filepath.Join(constants.ServiceAccountMountPath, constants.TalosconfigFilename),
|
||||
),
|
||||
)
|
||||
flag.StringVar(&endpoint, "talos.endpoint", "", "endpoint to use (overrides config)")
|
||||
flag.StringVar(&k8sEndpoint, "talos.k8sendpoint", "", "Kubernetes endpoint to use (overrides kubeconfig)")
|
||||
flag.StringVar(&provisionerName, "talos.provisioner", "", "Talos cluster provisioner to use, if not set cluster state is disabled")
|
||||
|
||||
@ -462,15 +462,12 @@ func (suite *UpgradeSuite) setupCluster() {
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
defaultTalosConfig, err := clientconfig.GetDefaultPath()
|
||||
suite.Require().NoError(err)
|
||||
|
||||
c, err := clientconfig.Open(defaultTalosConfig)
|
||||
c, err := clientconfig.Open("")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
c.Merge(suite.configBundle.TalosConfig())
|
||||
|
||||
suite.Require().NoError(c.Save(defaultTalosConfig))
|
||||
suite.Require().NoError(c.Save(""))
|
||||
|
||||
suite.clusterAccess = access.NewAdapter(suite.Cluster, provision.WithTalosConfig(suite.configBundle.TalosConfig()))
|
||||
|
||||
|
||||
44
internal/pkg/etcd/lock.go
Normal file
44
internal/pkg/etcd/lock.go
Normal file
@ -0,0 +1,44 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.etcd.io/etcd/client/v3/concurrency"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// WithLock executes the given function exclusively by acquiring an Etcd lock with the given key.
|
||||
func WithLock(ctx context.Context, key string, logger *zap.Logger, f func() error) error {
|
||||
etcdClient, err := NewLocalClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating etcd client: %w", err)
|
||||
}
|
||||
|
||||
defer etcdClient.Close() //nolint:errcheck
|
||||
|
||||
session, err := concurrency.NewSession(etcdClient.Client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating etcd session: %w", err)
|
||||
}
|
||||
|
||||
defer session.Close() //nolint:errcheck
|
||||
|
||||
mutex := concurrency.NewMutex(session, key)
|
||||
|
||||
logger.Debug("waiting for mutex", zap.String("key", key))
|
||||
|
||||
if err = mutex.Lock(ctx); err != nil {
|
||||
return fmt.Errorf("error acquiring mutex for key %s: %w", key, err)
|
||||
}
|
||||
|
||||
logger.Debug("mutex acquired", zap.String("key", key))
|
||||
|
||||
defer mutex.Unlock(ctx) //nolint:errcheck
|
||||
|
||||
return f()
|
||||
}
|
||||
@ -8,7 +8,6 @@ package installer
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -427,26 +426,11 @@ func (installer *Installer) apply(conn *Connection) error {
|
||||
}
|
||||
|
||||
func (installer *Installer) writeTalosconfig(list *tview.Flex, talosconfig *clientconfig.Config) error {
|
||||
path, err := clientconfig.GetDefaultPath()
|
||||
config, err := clientconfig.Open("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
|
||||
var config *clientconfig.Config
|
||||
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
config, err = clientconfig.ReadFrom(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
text := tview.NewTextView()
|
||||
addLines := func(lines ...string) {
|
||||
t := text.GetText(false)
|
||||
@ -457,17 +441,12 @@ func (installer *Installer) writeTalosconfig(list *tview.Flex, talosconfig *clie
|
||||
|
||||
addLines(
|
||||
"",
|
||||
fmt.Sprintf("Merging talosconfig into %s...", path),
|
||||
fmt.Sprintf("Merging talosconfig into %s...", config.Path().Path),
|
||||
)
|
||||
text.SetBackgroundColor(color)
|
||||
list.AddItem(text, 0, 1, false)
|
||||
|
||||
renames := []clientconfig.Rename{}
|
||||
if config != nil {
|
||||
renames = config.Merge(talosconfig)
|
||||
} else {
|
||||
config = talosconfig
|
||||
}
|
||||
renames := config.Merge(talosconfig)
|
||||
|
||||
for _, rename := range renames {
|
||||
addLines(fmt.Sprintf("Renamed %s.", rename.String()))
|
||||
@ -481,7 +460,7 @@ func (installer *Installer) writeTalosconfig(list *tview.Flex, talosconfig *clie
|
||||
config.Context = context
|
||||
addLines(fmt.Sprintf("Set current context to %q.", context))
|
||||
|
||||
err = config.Save(path)
|
||||
err = config.Save("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
378
pkg/kubernetes/inject/serviceaccount.go
Normal file
378
pkg/kubernetes/inject/serviceaccount.go
Normal file
@ -0,0 +1,378 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package inject
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
const (
|
||||
injectToEnv = false
|
||||
volumeName = "talos-secrets"
|
||||
|
||||
nameSuffix = "-talos-secrets"
|
||||
|
||||
apiVersionField = "apiVersion"
|
||||
kindField = "kind"
|
||||
metadataField = "metadata"
|
||||
namespaceField = "namespace"
|
||||
nameField = "name"
|
||||
|
||||
yamlSeparator = "---\n"
|
||||
)
|
||||
|
||||
// ServiceAccount takes a YAML with Kubernetes manifests and requested Talos roles as input
|
||||
// and injects Talos service accounts into them.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func ServiceAccount(reader io.Reader, roles []string) ([]byte, error) {
|
||||
var err error
|
||||
|
||||
objectSerializer := json.NewSerializerWithOptions(
|
||||
json.DefaultMetaFactory,
|
||||
nil,
|
||||
nil,
|
||||
json.SerializerOptions{
|
||||
Yaml: true,
|
||||
Pretty: true,
|
||||
Strict: true,
|
||||
},
|
||||
)
|
||||
|
||||
seenResourceIDs := make(map[string]struct{})
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
decoder := yaml.NewDecoder(reader)
|
||||
|
||||
// loop over all documents in a possibly YAML with multiple documents separated by ---
|
||||
for {
|
||||
var raw map[string]any
|
||||
|
||||
err = decoder.Decode(&raw)
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if raw == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var injected metav1.Object
|
||||
|
||||
injected, err = injectToObject(raw)
|
||||
if err != nil { // not a known resource with a PodSpec
|
||||
// if this is already a Talos ServiceAccount resource we have seen,
|
||||
// we keep it only if we have not seen it yet (means it belongs to the user, not injected by us)
|
||||
id := readResourceIDFromServiceAccount(raw)
|
||||
if id != "" {
|
||||
if _, ok := seenResourceIDs[id]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
seenResourceIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
err = yaml.NewEncoder(&buf).Encode(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf.WriteString(yamlSeparator)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// injectable resource type which contains a PodSpec
|
||||
|
||||
runtimeObject, ok := injected.(runtime.Object)
|
||||
if !ok {
|
||||
return nil, errors.New("injected object is not a runtime.Object")
|
||||
}
|
||||
|
||||
err = objectSerializer.Encode(runtimeObject, &buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf.WriteString(yamlSeparator)
|
||||
|
||||
id := readResourceIDFromObject(injected)
|
||||
|
||||
// inject service account for the resource
|
||||
if _, ok = seenResourceIDs[id]; !ok {
|
||||
sa := buildServiceAccount(injected.GetNamespace(), fmt.Sprintf("%s%s", injected.GetName(), nameSuffix), roles)
|
||||
|
||||
err = yaml.NewEncoder(&buf).Encode(sa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf.WriteString(yamlSeparator)
|
||||
|
||||
// mark resource as seen
|
||||
seenResourceIDs[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func buildServiceAccount(namespace string, name string, roles []string) map[string]any {
|
||||
metadata := map[string]any{
|
||||
nameField: name,
|
||||
}
|
||||
|
||||
if namespace != "" {
|
||||
metadata[namespaceField] = namespace
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
apiVersionField: fmt.Sprintf(
|
||||
"%s/%s",
|
||||
constants.ServiceAccountResourceGroup,
|
||||
constants.ServiceAccountResourceVersion,
|
||||
),
|
||||
kindField: constants.ServiceAccountResourceKind,
|
||||
metadataField: metadata,
|
||||
"spec": map[string]any{
|
||||
"roles": roles,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func isServiceAccount(raw map[string]any) bool {
|
||||
apiVersionKind, err := readResourceAPIVersionKind(raw)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return apiVersionKind == fmt.Sprintf(
|
||||
"%s/%s/%s",
|
||||
constants.ServiceAccountResourceGroup,
|
||||
constants.ServiceAccountResourceVersion,
|
||||
constants.ServiceAccountResourceKind,
|
||||
)
|
||||
}
|
||||
|
||||
// injectToDocument takes a single YAML document and attempts to inject a ServiceAccount
|
||||
// into it if it is a known Kubernetes resource type which contains a corev1.PodSpec.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func injectToObject(raw map[string]any) (metav1.Object, error) {
|
||||
var err error
|
||||
|
||||
apiVersionKind, err := readResourceAPIVersionKind(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch apiVersionKind {
|
||||
case "v1/Pod":
|
||||
return injectToPodSpecObject[corev1.Pod](raw, func(obj *corev1.Pod) *corev1.PodSpec {
|
||||
return &obj.Spec
|
||||
})
|
||||
|
||||
case "apps/v1/Deployment":
|
||||
return injectToPodSpecObject[appsv1.Deployment](raw, func(obj *appsv1.Deployment) *corev1.PodSpec {
|
||||
return &obj.Spec.Template.Spec
|
||||
})
|
||||
|
||||
case "apps/v1/StatefulSet":
|
||||
return injectToPodSpecObject[appsv1.StatefulSet](raw, func(obj *appsv1.StatefulSet) *corev1.PodSpec {
|
||||
return &obj.Spec.Template.Spec
|
||||
})
|
||||
|
||||
case "apps/v1/DaemonSet":
|
||||
return injectToPodSpecObject[appsv1.DaemonSet](raw, func(obj *appsv1.DaemonSet) *corev1.PodSpec {
|
||||
return &obj.Spec.Template.Spec
|
||||
})
|
||||
|
||||
case "batch/v1/Job":
|
||||
return injectToPodSpecObject[batchv1.Job](raw, func(obj *batchv1.Job) *corev1.PodSpec {
|
||||
return &obj.Spec.Template.Spec
|
||||
})
|
||||
|
||||
case "batch/v1/CronJob":
|
||||
return injectToPodSpecObject[batchv1.CronJob](raw, func(obj *batchv1.CronJob) *corev1.PodSpec {
|
||||
return &obj.Spec.JobTemplate.Spec.Template.Spec
|
||||
})
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported object type: %s", apiVersionKind)
|
||||
}
|
||||
|
||||
func injectToPodSpecObject[T any](raw map[string]any, podSpecFunc func(*T) *corev1.PodSpec) (*T, error) {
|
||||
objectName, nameFound, err := unstructured.NestedString(raw, metadataField, nameField)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !nameFound {
|
||||
return nil, errors.New("object has no name")
|
||||
}
|
||||
|
||||
var obj T
|
||||
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructuredWithValidation(raw, &obj, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
injectToPodSpec(fmt.Sprintf("%s%s", objectName, nameSuffix), podSpecFunc(&obj))
|
||||
|
||||
return &obj, nil
|
||||
}
|
||||
|
||||
func readResourceAPIVersionKind(raw map[string]any) (string, error) {
|
||||
apiVersion, found, err := unstructured.NestedString(raw, apiVersionField)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return "", fmt.Errorf("%s not found", apiVersionField)
|
||||
}
|
||||
|
||||
kind, found, err := unstructured.NestedString(raw, kindField)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return "", fmt.Errorf("%s not found", kindField)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", apiVersion, kind), nil
|
||||
}
|
||||
|
||||
func readResourceIDFromObject(obj metav1.Object) string {
|
||||
if obj.GetNamespace() == "" {
|
||||
return obj.GetName()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName())
|
||||
}
|
||||
|
||||
func readResourceIDFromServiceAccount(raw map[string]any) string {
|
||||
if !isServiceAccount(raw) {
|
||||
return ""
|
||||
}
|
||||
|
||||
name, nameFound, err := unstructured.NestedString(raw, metadataField, nameField)
|
||||
if err != nil || !nameFound {
|
||||
return ""
|
||||
}
|
||||
|
||||
nameTrimmed := strings.TrimSuffix(name, nameSuffix)
|
||||
|
||||
ns, nsFound, err := unstructured.NestedString(raw, metadataField, namespaceField)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if nsFound {
|
||||
return fmt.Sprintf("%s/%s", ns, nameTrimmed)
|
||||
}
|
||||
|
||||
return nameTrimmed
|
||||
}
|
||||
|
||||
func injectToPodSpec(secretName string, podSpec *corev1.PodSpec) {
|
||||
podSpec.Volumes = injectToVolumes(secretName, podSpec.Volumes)
|
||||
podSpec.InitContainers = injectToContainers(podSpec.InitContainers)
|
||||
podSpec.Containers = injectToContainers(podSpec.Containers)
|
||||
}
|
||||
|
||||
func injectToVolumes(name string, volumes []corev1.Volume) []corev1.Volume {
|
||||
result := make([]corev1.Volume, 0, len(volumes))
|
||||
|
||||
for _, volume := range volumes {
|
||||
if volume.Name != volumeName {
|
||||
result = append(result, volume)
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, corev1.Volume{
|
||||
Name: volumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: name,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func injectToContainers(containers []corev1.Container) []corev1.Container {
|
||||
result := make([]corev1.Container, 0, len(containers))
|
||||
|
||||
for _, container := range containers {
|
||||
injectToContainer(&container)
|
||||
|
||||
result = append(result, container)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func injectToContainer(container *corev1.Container) {
|
||||
volumeMounts := make([]corev1.VolumeMount, 0, len(container.VolumeMounts))
|
||||
|
||||
for _, mount := range container.VolumeMounts {
|
||||
if mount.Name != volumeName {
|
||||
volumeMounts = append(volumeMounts, mount)
|
||||
}
|
||||
}
|
||||
|
||||
volumeMounts = append(volumeMounts, corev1.VolumeMount{
|
||||
Name: volumeName,
|
||||
MountPath: constants.ServiceAccountMountPath,
|
||||
})
|
||||
|
||||
container.VolumeMounts = volumeMounts
|
||||
|
||||
if injectToEnv {
|
||||
container.Env = injectToContainerEnv(container.Env)
|
||||
}
|
||||
}
|
||||
|
||||
func injectToContainerEnv(env []corev1.EnvVar) []corev1.EnvVar {
|
||||
result := make([]corev1.EnvVar, 0, len(env))
|
||||
|
||||
for _, envVar := range env {
|
||||
if envVar.Name != constants.TalosConfigEnvVar {
|
||||
result = append(result, envVar)
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, corev1.EnvVar{
|
||||
Name: constants.TalosConfigEnvVar,
|
||||
Value: filepath.Join(constants.ServiceAccountMountPath, constants.TalosconfigFilename),
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
@ -13,13 +13,16 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/talos-systems/crypto/x509"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config represents the client configuration file (talosconfig).
|
||||
type Config struct {
|
||||
Context string `yaml:"context"`
|
||||
Contexts map[string]*Context `yaml:"contexts"`
|
||||
|
||||
// path is the config Path config is read from.
|
||||
path Path
|
||||
}
|
||||
|
||||
// NewConfig returns the client configuration file with a single context.
|
||||
@ -61,21 +64,50 @@ func (c *Context) upgrade() {
|
||||
}
|
||||
|
||||
// Open reads the config and initializes a Config struct.
|
||||
func Open(p string) (c *Config, err error) {
|
||||
if err = ensure(p); err != nil {
|
||||
// If path is explicitly set, it will be used.
|
||||
// If not, the default path rules will be used.
|
||||
func Open(path string) (*Config, error) {
|
||||
var (
|
||||
confPath Path
|
||||
err error
|
||||
)
|
||||
|
||||
if path != "" { // path is explicitly specified, ensure that is created and use it
|
||||
confPath = Path{
|
||||
Path: path,
|
||||
WriteAllowed: true,
|
||||
}
|
||||
|
||||
err = ensure(confPath.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else { // path is implicit, get the first already existing & readable path or ensure that it is created
|
||||
confPath, err = firstValidPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
config, err := fromFile(confPath.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var f *os.File
|
||||
config.path = confPath
|
||||
|
||||
f, err = os.Open(p)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func fromFile(path string) (*Config, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer f.Close() //nolint:errcheck
|
||||
defer file.Close() //nolint:errcheck
|
||||
|
||||
return ReadFrom(f)
|
||||
return ReadFrom(file)
|
||||
}
|
||||
|
||||
// FromString returns a config from a string.
|
||||
@ -102,18 +134,37 @@ func ReadFrom(r io.Reader) (c *Config, err error) {
|
||||
}
|
||||
|
||||
// Save writes the config to disk.
|
||||
func (c *Config) Save(p string) (err error) {
|
||||
configBytes, err := c.Bytes()
|
||||
if err != nil {
|
||||
return
|
||||
// If the path is not explicitly set, the default path rules will be used.
|
||||
func (c *Config) Save(path string) error {
|
||||
var err error
|
||||
|
||||
if path != "" { // path is explicitly specified, use it
|
||||
c.path = Path{
|
||||
Path: path,
|
||||
WriteAllowed: true,
|
||||
}
|
||||
} else if c.path.Path == "" { // path is implicit and is not set on config, get the first already existing & writable path or create it
|
||||
c.path, err = firstValidPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(filepath.Dir(p), 0o700); err != nil {
|
||||
if !c.path.WriteAllowed {
|
||||
return fmt.Errorf("not allowed to write to config: %s", c.path.Path)
|
||||
}
|
||||
|
||||
configBytes, err := c.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.WriteFile(p, configBytes, 0o600); err != nil {
|
||||
return
|
||||
if err = os.MkdirAll(filepath.Dir(c.path.Path), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.WriteFile(c.path.Path, configBytes, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -124,6 +175,11 @@ func (c *Config) Bytes() ([]byte, error) {
|
||||
return yaml.Marshal(c)
|
||||
}
|
||||
|
||||
// Path returns the filesystem path config was read from.
|
||||
func (c *Config) Path() Path {
|
||||
return c.path
|
||||
}
|
||||
|
||||
// Rename describes context rename during merge.
|
||||
type Rename struct {
|
||||
From string
|
||||
@ -175,14 +231,14 @@ func (c *Config) Merge(cfg *Config) []Rename {
|
||||
return renames
|
||||
}
|
||||
|
||||
func ensure(filename string) (err error) {
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
func ensure(path string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
config := &Config{
|
||||
Context: "",
|
||||
Contexts: map[string]*Context{},
|
||||
}
|
||||
|
||||
return config.Save(filename)
|
||||
return config.Save(path)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@ -5,12 +5,21 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
// Path represents a path to a configuration file.
|
||||
type Path struct {
|
||||
// Path is the filesystem path of the config.
|
||||
Path string
|
||||
// WriteAllowed is true if the path is allowed to be written.
|
||||
WriteAllowed bool
|
||||
}
|
||||
|
||||
// GetTalosDirectory returns path to Talos directory (~/.talos).
|
||||
func GetTalosDirectory() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
@ -18,30 +27,70 @@ func GetTalosDirectory() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(home, ".talos"), nil
|
||||
return filepath.Join(home, constants.TalosDir), nil
|
||||
}
|
||||
|
||||
// GetDefaultPath returns default path to Talos config.
|
||||
func GetDefaultPath() (string, error) {
|
||||
if path, ok := os.LookupEnv(constants.TalosConfigEnvVar); ok {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
talosSAPath := filepath.Join(constants.ServiceAccountMountPath, constants.ServiceAccountTalosconfigFilename)
|
||||
|
||||
_, err := os.Stat(talosSAPath)
|
||||
if err != nil && !os.IsNotExist(err) && !os.IsPermission(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return talosSAPath, nil
|
||||
}
|
||||
|
||||
// GetDefaultPaths returns the list of config file paths in order of priority.
|
||||
func GetDefaultPaths() ([]Path, error) {
|
||||
talosDir, err := GetTalosDirectory()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return filepath.Join(talosDir, "config"), nil
|
||||
result := make([]Path, 0, 3)
|
||||
|
||||
if path, ok := os.LookupEnv(constants.TalosConfigEnvVar); ok {
|
||||
result = append(result, Path{
|
||||
Path: path,
|
||||
WriteAllowed: true,
|
||||
})
|
||||
}
|
||||
|
||||
result = append(
|
||||
result,
|
||||
Path{
|
||||
Path: filepath.Join(talosDir, constants.TalosconfigFilename),
|
||||
WriteAllowed: true,
|
||||
},
|
||||
Path{
|
||||
Path: filepath.Join(constants.ServiceAccountMountPath, constants.TalosconfigFilename),
|
||||
WriteAllowed: false,
|
||||
},
|
||||
)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// firstValidPath iterates over the default paths and returns the first one that exists and readable.
|
||||
// If no path is found, it will ensure that the first path that allows writes is created and returned.
|
||||
// If no path is found that is writable, an error is returned.
|
||||
func firstValidPath() (Path, error) {
|
||||
paths, err := GetDefaultPaths()
|
||||
if err != nil {
|
||||
return Path{}, err
|
||||
}
|
||||
|
||||
var firstWriteAllowed Path
|
||||
|
||||
for _, path := range paths {
|
||||
_, err = os.Stat(path.Path)
|
||||
if err == nil {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
if firstWriteAllowed.Path == "" && path.WriteAllowed {
|
||||
firstWriteAllowed = path
|
||||
}
|
||||
}
|
||||
|
||||
if firstWriteAllowed.Path == "" {
|
||||
return Path{}, fmt.Errorf("no valid config paths found")
|
||||
}
|
||||
|
||||
err = ensure(firstWriteAllowed.Path)
|
||||
if err != nil {
|
||||
return Path{}, err
|
||||
}
|
||||
|
||||
return firstWriteAllowed, nil
|
||||
}
|
||||
|
||||
@ -92,12 +92,7 @@ func WithEndpoints(endpoints ...string) OptionFunc {
|
||||
// Additionally use WithContextName to select a context other than the default.
|
||||
func WithDefaultConfig() OptionFunc {
|
||||
return func(o *Options) (err error) {
|
||||
defaultConfigPath, err := clientconfig.GetDefaultPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("no client configuration provided and no default path found: %w", err)
|
||||
}
|
||||
|
||||
return WithConfigFromFile(defaultConfigPath)(o)
|
||||
return WithConfigFromFile("")(o)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -438,7 +438,6 @@ func (c *ClusterDiscoveryConfig) Validate(clusterCfg *ClusterConfig) error {
|
||||
// ValidateNetworkDevices runs the specified validation checks specific to the
|
||||
// network devices.
|
||||
func ValidateNetworkDevices(d *Device, pairedInterfaces map[string]string, checks ...NetworkDeviceCheck) ([]string, error) {
|
||||
// todo utku
|
||||
var result *multierror.Error
|
||||
|
||||
if d == nil {
|
||||
|
||||
@ -349,9 +349,12 @@ const (
|
||||
// EtcdTalosEtcdUpgradeMutex is the etcd mutex prefix to be used to set an etcd upgrade lock.
|
||||
EtcdTalosEtcdUpgradeMutex = EtcdRootTalosKey + ":etcdUpgradeMutex"
|
||||
|
||||
// EtcdTalosManifestApplyMutex is the etcd election .
|
||||
// EtcdTalosManifestApplyMutex is the etcd mutex prefix used by manifest apply controller.
|
||||
EtcdTalosManifestApplyMutex = EtcdRootTalosKey + ":manifestApplyMutex"
|
||||
|
||||
// EtcdTalosServiceAccountCRDControllerMutex is the etcd mutex prefix used by Talos ServiceAccount crd controller.
|
||||
EtcdTalosServiceAccountCRDControllerMutex = EtcdRootTalosKey + ":serviceAccountCRDController"
|
||||
|
||||
// EtcdImage is the reposistory for the etcd image.
|
||||
EtcdImage = "gcr.io/etcd-development/etcd"
|
||||
|
||||
@ -708,14 +711,35 @@ const (
|
||||
// KubernetesTalosAPIServiceNamespace is the namespace of the Kubernetes service to access Talos API.
|
||||
KubernetesTalosAPIServiceNamespace = "default"
|
||||
|
||||
// TalosDir is the default name of the Talos directory under user home.
|
||||
TalosDir = ".talos"
|
||||
|
||||
// TalosconfigFilename is the file name of Talosconfig under TalosDir or under ServiceAccountMountPath inside a pod.
|
||||
TalosconfigFilename = "config"
|
||||
|
||||
// KubernetesTalosProvider is the name of the Talos provider as a Kubernetes label.
|
||||
KubernetesTalosProvider = "talos.dev"
|
||||
|
||||
// ServiceAccountTalosconfigFilename is the file name of Talosconfig when it is injected into a pod.
|
||||
ServiceAccountTalosconfigFilename = "config"
|
||||
// ServiceAccountResourceGroup is the group name of the Talos service account CRD.
|
||||
ServiceAccountResourceGroup = "talos.dev"
|
||||
|
||||
// ServiceAccountResourceVersion is the version of the Talos service account CRD.
|
||||
ServiceAccountResourceVersion = "v1alpha1"
|
||||
|
||||
// ServiceAccountResourceKind is the kind name of the Talos service account CRD.
|
||||
ServiceAccountResourceKind = "ServiceAccount"
|
||||
|
||||
// ServiceAccountResourceSingular is the singular name of the Talos service account CRD.
|
||||
ServiceAccountResourceSingular = "serviceaccount"
|
||||
|
||||
// ServiceAccountResourceShortName is the short name of the service account CRD.
|
||||
ServiceAccountResourceShortName = "tsa"
|
||||
|
||||
// ServiceAccountResourcePlural is the plural name of the service account CRD.
|
||||
ServiceAccountResourcePlural = ServiceAccountResourceSingular + "s"
|
||||
|
||||
// ServiceAccountMountPath is the path of the directory in which the Talos service account secrets are mounted.
|
||||
ServiceAccountMountPath = "/var/run/secrets/talos.dev/"
|
||||
ServiceAccountMountPath = "/var/run/secrets/talos.dev"
|
||||
|
||||
// DefaultTrustedCAFile is the default path to the trusted CA file.
|
||||
DefaultTrustedCAFile = "/etc/ssl/certs/ca-certificates"
|
||||
|
||||
157
website/content/v1.2/advanced/talos-api-access-from-k8s.md
Normal file
157
website/content/v1.2/advanced/talos-api-access-from-k8s.md
Normal file
@ -0,0 +1,157 @@
|
||||
---
|
||||
title: "Talos API access from Kubernetes"
|
||||
description: "How to access Talos API from within Kubernetes."
|
||||
aliases:
|
||||
- ../guides/talos-api-access-from-k8s
|
||||
---
|
||||
|
||||
In this guide, we will enable the Talos feature to access the Talos API from within Kubernetes.
|
||||
|
||||
## Enabling the Feature
|
||||
|
||||
Edit the machine configuration to enable the feature, specifying the Kubernetes namespaces from which Talos API
|
||||
can be accessed and the allowed Talos API roles.
|
||||
|
||||
```bash
|
||||
talosctl -n 172.20.0.2 edit machineconfig
|
||||
```
|
||||
|
||||
Configure the `kubernetesTalosAPIAccess` like the following:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
machine:
|
||||
features:
|
||||
kubernetesTalosAPIAccess:
|
||||
enabled: true
|
||||
allowedRoles:
|
||||
- os:reader
|
||||
allowedKubernetesNamespaces:
|
||||
- default
|
||||
```
|
||||
|
||||
## Injecting Talos ServiceAccount into manifests
|
||||
|
||||
Create the following manifest file `deployment.yaml`:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: talos-api-access
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: talos-api-access
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: talos-api-access
|
||||
spec:
|
||||
containers:
|
||||
- name: talos-api-access
|
||||
image: alpine:3
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
wget -O /usr/local/bin/talosctl https://github.com/siderolabs/talos/releases/download/<talos version>/talosctl-linux-amd64
|
||||
chmod +x /usr/local/bin/talosctl
|
||||
while true; talosctl -n 172.20.0.2 version; do sleep 1; done
|
||||
```
|
||||
|
||||
**Note:** make sure that you replace the IP `172.20.0.2` with a valid Talos node IP.
|
||||
|
||||
Use `talosctl inject serviceaccount` command to inject the Talos ServiceAccount into the manifest.
|
||||
|
||||
```bash
|
||||
talosctl inject serviceaccount -f deployment.yaml > deployment-injected.yaml
|
||||
```
|
||||
|
||||
Inspect the generated manifest:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: talos-api-access
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: talos-api-access
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: talos-api-access
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
wget -O /usr/local/bin/talosctl https://github.com/siderolabs/talos/releases/download/<talos version>/talosctl-linux-amd64
|
||||
chmod +x /usr/local/bin/talosctl
|
||||
while true; talosctl -n 172.20.0.2 version; do sleep 1; done
|
||||
image: alpine:3
|
||||
name: talos-api-access
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/secrets/talos.dev
|
||||
name: talos-secrets
|
||||
tolerations:
|
||||
- operator: Exists
|
||||
volumes:
|
||||
- name: talos-secrets
|
||||
secret:
|
||||
secretName: talos-api-access-talos-secrets
|
||||
status: {}
|
||||
---
|
||||
apiVersion: talos.dev/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: talos-api-access-talos-secrets
|
||||
spec:
|
||||
roles:
|
||||
- os:reader
|
||||
---
|
||||
```
|
||||
|
||||
As you can notice, your deployment manifest is now injected with the Talos ServiceAccount.
|
||||
|
||||
## Testing API Access
|
||||
|
||||
Apply the new manifest into `default` namespace:
|
||||
|
||||
```bash
|
||||
kubectl apply -n default -f deployment-injected.yaml
|
||||
```
|
||||
|
||||
Follow the logs of the pods belong to the deployment:
|
||||
|
||||
```bash
|
||||
kubectl logs -n default -f -l app=talos-api-access
|
||||
```
|
||||
|
||||
You'll see a repeating output similar to the following:
|
||||
|
||||
```text
|
||||
Client:
|
||||
Tag: <talos version>
|
||||
SHA: ....
|
||||
Built:
|
||||
Go version: go1.18.4
|
||||
OS/Arch: linux/amd64
|
||||
Server:
|
||||
NODE: 172.20.0.2
|
||||
Tag: <talos version>
|
||||
SHA: ...
|
||||
Built:
|
||||
Go version: go1.18.4
|
||||
OS/Arch: linux/amd64
|
||||
Enabled: RBAC
|
||||
```
|
||||
|
||||
This means that the pod can talk to Talos API of node 172.20.0.2 successfully.
|
||||
@ -31,7 +31,7 @@ talosctl apply-config [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -71,7 +71,7 @@ talosctl bootstrap [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -162,7 +162,7 @@ talosctl cluster create [flags]
|
||||
-n, --nodes strings target the specified nodes
|
||||
--provisioner string Talos cluster provisioner to use (default "docker")
|
||||
--state string directory path to store cluster state (default "/home/user/.talos/clusters")
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -192,7 +192,7 @@ talosctl cluster destroy [flags]
|
||||
-n, --nodes strings target the specified nodes
|
||||
--provisioner string Talos cluster provisioner to use (default "docker")
|
||||
--state string directory path to store cluster state (default "/home/user/.talos/clusters")
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -222,7 +222,7 @@ talosctl cluster show [flags]
|
||||
-n, --nodes strings target the specified nodes
|
||||
--provisioner string Talos cluster provisioner to use (default "docker")
|
||||
--state string directory path to store cluster state (default "/home/user/.talos/clusters")
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -248,7 +248,7 @@ A collection of commands for managing local docker-based or QEMU-based clusters
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -321,7 +321,7 @@ talosctl completion SHELL [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -351,7 +351,7 @@ talosctl config add <context> [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -378,7 +378,7 @@ talosctl config context <context> [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -405,7 +405,7 @@ talosctl config contexts [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -432,7 +432,7 @@ talosctl config endpoint <endpoint>... [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -459,7 +459,7 @@ talosctl config info [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -490,7 +490,7 @@ talosctl config merge <from> [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -519,7 +519,7 @@ talosctl config new [<path>] [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -546,7 +546,7 @@ talosctl config node <endpoint>... [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -569,7 +569,7 @@ Manage the client configuration file (talosconfig)
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -605,7 +605,7 @@ talosctl conformance kubernetes [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -628,7 +628,7 @@ Run conformance tests
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -657,7 +657,7 @@ talosctl containers [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -695,7 +695,7 @@ talosctl copy <src-path> -|<local-path> [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -739,7 +739,7 @@ talosctl dashboard [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -767,7 +767,7 @@ talosctl disks [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -796,7 +796,7 @@ talosctl dmesg [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -836,7 +836,7 @@ talosctl edit <type> [<id>] [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -863,7 +863,7 @@ talosctl etcd forfeit-leadership [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -890,7 +890,7 @@ talosctl etcd leave [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -917,7 +917,7 @@ talosctl etcd members [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -950,7 +950,7 @@ talosctl etcd remove-member <hostname> [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -977,7 +977,7 @@ talosctl etcd snapshot <path> [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1000,7 +1000,7 @@ Manage etcd
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1035,7 +1035,7 @@ talosctl events [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1065,7 +1065,7 @@ talosctl gen ca [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1117,7 +1117,7 @@ talosctl gen config <cluster name> <cluster endpoint> [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1148,7 +1148,7 @@ talosctl gen crt [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1178,7 +1178,7 @@ talosctl gen csr [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1206,7 +1206,7 @@ talosctl gen key [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1235,7 +1235,7 @@ talosctl gen keypair [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1266,7 +1266,7 @@ talosctl gen secrets [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1289,7 +1289,7 @@ Generate CAs, certificates, and private keys
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1332,7 +1332,7 @@ talosctl get <type> [<id>] [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1366,7 +1366,7 @@ talosctl health [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1393,13 +1393,76 @@ talosctl images [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [talosctl](#talosctl) - A CLI for out-of-band management of Kubernetes nodes created by Talos
|
||||
|
||||
## talosctl inject serviceaccount
|
||||
|
||||
Inject Talos API ServiceAccount into Kubernetes manifests
|
||||
|
||||
```
|
||||
talosctl inject serviceaccount [--roles='<ROLE_1>,<ROLE_2>'] -f <manifest.yaml> [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
talosctl inject serviceaccount --roles="os:admin" -f deployment.yaml > deployment-injected.yaml
|
||||
|
||||
Alternatively, stdin can be piped to the command:
|
||||
cat deployment.yaml | talosctl inject serviceaccount --roles="os:admin" -f - > deployment-injected.yaml
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-f, --file string file with Kubernetes manifests to be injected with ServiceAccount
|
||||
-h, --help help for serviceaccount
|
||||
-r, --roles strings roles to add to the generated ServiceAccount manifests (default [os:reader])
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [talosctl inject](#talosctl-inject) - Inject Talos API resources into Kubernetes manifests
|
||||
|
||||
## talosctl inject
|
||||
|
||||
Inject Talos API resources into Kubernetes manifests
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for inject
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [talosctl](#talosctl) - A CLI for out-of-band management of Kubernetes nodes created by Talos
|
||||
* [talosctl inject serviceaccount](#talosctl-inject-serviceaccount) - Inject Talos API ServiceAccount into Kubernetes manifests
|
||||
|
||||
## talosctl inspect dependencies
|
||||
|
||||
Inspect controller-resource dependencies as graphviz graph.
|
||||
@ -1431,7 +1494,7 @@ talosctl inspect dependencies [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1454,7 +1517,7 @@ Inspect internals of Talos
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1491,7 +1554,7 @@ talosctl kubeconfig [local-path] [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1526,7 +1589,7 @@ talosctl list [path] [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1556,7 +1619,7 @@ talosctl logs <service name> [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1584,7 +1647,7 @@ talosctl memory [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1611,7 +1674,7 @@ talosctl mounts [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1644,7 +1707,7 @@ talosctl patch <type> [<id>] [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1705,7 +1768,7 @@ talosctl pcap [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1734,7 +1797,7 @@ talosctl processes [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1761,7 +1824,7 @@ talosctl read <path> [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1789,7 +1852,7 @@ talosctl reboot [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1819,7 +1882,7 @@ talosctl reset [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1847,7 +1910,7 @@ talosctl restart <id> [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1874,7 +1937,7 @@ talosctl rollback [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1907,7 +1970,7 @@ talosctl service [<id> [start|stop|restart|status]] [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1935,7 +1998,7 @@ talosctl shutdown [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -1963,7 +2026,7 @@ talosctl stats [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -2015,7 +2078,7 @@ talosctl support [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -2043,7 +2106,7 @@ talosctl time [--check server] [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -2074,7 +2137,7 @@ talosctl upgrade [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -2110,7 +2173,7 @@ talosctl upgrade-k8s [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -2141,7 +2204,7 @@ talosctl usage [path1] [path2] ... [pathN] [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -2171,7 +2234,7 @@ talosctl validate [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -2201,7 +2264,7 @@ talosctl version [flags]
|
||||
--context string Context to be used in command
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -2219,7 +2282,7 @@ A CLI for out-of-band management of Kubernetes nodes created by Talos
|
||||
-e, --endpoints strings override default endpoints in Talos configuration
|
||||
-h, --help help for talosctl
|
||||
-n, --nodes strings target the specified nodes
|
||||
--talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config")
|
||||
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@ -2242,6 +2305,7 @@ A CLI for out-of-band management of Kubernetes nodes created by Talos
|
||||
* [talosctl get](#talosctl-get) - Get a specific resource or list of resources.
|
||||
* [talosctl health](#talosctl-health) - Check cluster health
|
||||
* [talosctl images](#talosctl-images) - List the default images used by Talos
|
||||
* [talosctl inject](#talosctl-inject) - Inject Talos API resources into Kubernetes manifests
|
||||
* [talosctl inspect](#talosctl-inspect) - Inspect internals of Talos
|
||||
* [talosctl kubeconfig](#talosctl-kubeconfig) - Download the admin kubeconfig from the node
|
||||
* [talosctl list](#talosctl-list) - Retrieve a directory listing
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user