Skip to content

Commit 98be923

Browse files
committed
Let API subcommand accept the runtime config via stdin
The API subcommand is not intended to run as a standalone process. It's always run under the supervision of a k0s controller process. Therefore, the usual configuration loading process is inappropriate. Instead, accept the runtime configuration via stdin. This way, there's no way to fallback to a generated default configuration, or to load the configuration from a possibly existing default configuration file that has nothing to do with the one used by the supervising process. Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
1 parent c4528f0 commit 98be923

File tree

6 files changed

+88
-47
lines changed

6 files changed

+88
-47
lines changed

cmd/api/api.go

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"encoding/json"
2323
"errors"
2424
"fmt"
25+
"io"
2526
"net/http"
2627
"os"
2728
"path"
@@ -42,37 +43,61 @@ import (
4243

4344
"github.com/sirupsen/logrus"
4445
"github.com/spf13/cobra"
46+
"github.com/spf13/pflag"
4547
)
4648

4749
func NewAPICmd() *cobra.Command {
4850
cmd := &cobra.Command{
4951
Use: "api",
5052
Short: "Run the controller API",
51-
Args: cobra.NoArgs,
53+
Long: `Run the controller API.
54+
Reads the runtime configuration from standard input.`,
55+
Args: cobra.NoArgs,
5256
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
5357
logrus.SetOutput(cmd.OutOrStdout())
5458
internallog.SetInfoLevel()
5559
return config.CallParentPersistentPreRun(cmd, args)
5660
},
5761
RunE: func(cmd *cobra.Command, _ []string) error {
58-
opts, err := config.GetCmdOpts(cmd)
59-
if err != nil {
60-
return err
61-
}
62+
var run func() error
6263

63-
run, err := buildServer(opts.K0sVars)
64-
if err != nil {
64+
if runtimeConfig, err := loadRuntimeConfig(cmd.InOrStdin()); err != nil {
65+
return err
66+
} else if run, err = buildServer(runtimeConfig.Spec.K0sVars, runtimeConfig.Spec.NodeConfig); err != nil {
6567
return err
6668
}
6769

6870
return run()
6971
},
7072
}
71-
cmd.PersistentFlags().AddFlagSet(config.GetPersistentFlagSet())
73+
74+
flags := cmd.Flags()
75+
config.GetPersistentFlagSet().VisitAll(func(f *pflag.Flag) {
76+
switch f.Name {
77+
case "debug", "debugListenOn", "verbose":
78+
flags.AddFlag(f)
79+
}
80+
})
81+
7282
return cmd
7383
}
7484

75-
func buildServer(k0sVars *config.CfgVars) (func() error, error) {
85+
func loadRuntimeConfig(stdin io.Reader) (*config.RuntimeConfig, error) {
86+
logrus.Info("Reading runtime configuration from standard input ...")
87+
bytes, err := io.ReadAll(stdin)
88+
if err != nil {
89+
return nil, fmt.Errorf("failed to read from standard input: %w", err)
90+
}
91+
92+
runtimeConfig, err := config.ParseRuntimeConfig(bytes)
93+
if err != nil {
94+
return nil, fmt.Errorf("failed to load runtime configuration: %w", err)
95+
}
96+
97+
return runtimeConfig, nil
98+
}
99+
100+
func buildServer(k0sVars *config.CfgVars, nodeConfig *v1beta1.ClusterConfig) (func() error, error) {
76101
// Single kube client for whole lifetime of the API
77102
client, err := kubeutil.NewClientFromFile(k0sVars.AdminKubeConfigPath)
78103
if err != nil {
@@ -82,10 +107,6 @@ func buildServer(k0sVars *config.CfgVars) (func() error, error) {
82107

83108
prefix := "/v1beta1"
84109
mux := http.NewServeMux()
85-
nodeConfig, err := k0sVars.NodeConfig()
86-
if err != nil {
87-
return nil, err
88-
}
89110
storage := nodeConfig.Spec.Storage
90111

91112
if storage.Type == v1beta1.EtcdStorageType && !storage.Etcd.IsExternalClusterUsed() {

cmd/controller/controller.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func (c *command) start(ctx context.Context) error {
162162
return fmt.Errorf("failed to initialize runtime config: %w", err)
163163
}
164164
defer func() {
165-
if err := rtc.Cleanup(); err != nil {
165+
if err := rtc.Spec.Cleanup(); err != nil {
166166
logrus.WithError(err).Warn("Failed to cleanup runtime config")
167167
}
168168
}()
@@ -326,10 +326,7 @@ func (c *command) start(ctx context.Context) error {
326326
}
327327

328328
if !c.SingleNode && !slices.Contains(c.DisableComponents, constant.ControlAPIComponentName) {
329-
nodeComponents.Add(ctx, &controller.K0SControlAPI{
330-
ConfigPath: c.CfgFile,
331-
K0sVars: c.K0sVars,
332-
})
329+
nodeComponents.Add(ctx, &controller.K0SControlAPI{RuntimeConfig: rtc})
333330
}
334331

335332
if !slices.Contains(c.DisableComponents, constant.CsrApproverComponentName) {

pkg/component/controller/k0scontrolapi.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,21 @@ limitations under the License.
1717
package controller
1818

1919
import (
20+
"bytes"
2021
"context"
22+
"io"
2123
"os"
2224

2325
"github.com/k0sproject/k0s/pkg/component/manager"
2426
"github.com/k0sproject/k0s/pkg/config"
2527
"github.com/k0sproject/k0s/pkg/supervisor"
28+
"sigs.k8s.io/yaml"
2629
)
2730

2831
// K0SControlAPI implements the k0s control API component
2932
type K0SControlAPI struct {
30-
ConfigPath string
31-
K0sVars *config.CfgVars
33+
RuntimeConfig *config.RuntimeConfig
34+
3235
supervisor supervisor.Supervisor
3336
}
3437

@@ -48,15 +51,19 @@ func (m *K0SControlAPI) Start(_ context.Context) error {
4851
if err != nil {
4952
return err
5053
}
54+
55+
runtimeConfig, err := yaml.Marshal(m.RuntimeConfig)
56+
if err != nil {
57+
return err
58+
}
59+
5160
m.supervisor = supervisor.Supervisor{
5261
Name: "k0s-control-api",
5362
BinPath: selfExe,
54-
RunDir: m.K0sVars.RunDir,
55-
DataDir: m.K0sVars.DataDir,
56-
Args: []string{
57-
"api",
58-
"--data-dir=" + m.K0sVars.DataDir,
59-
},
63+
RunDir: m.RuntimeConfig.Spec.K0sVars.RunDir,
64+
DataDir: m.RuntimeConfig.Spec.K0sVars.DataDir,
65+
Args: []string{"api"},
66+
Stdin: func() io.Reader { return bytes.NewReader(runtimeConfig) },
6067
}
6168

6269
return m.supervisor.Supervise()

pkg/config/runtime.go

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const (
4242
var (
4343
ErrK0sNotRunning = errors.New("k0s is not running")
4444
ErrK0sAlreadyRunning = errors.New("an instance of k0s is already running")
45-
ErrInvalidRuntimeConfig = errors.New("invalid runtime config")
45+
ErrInvalidRuntimeConfig = errors.New("invalid runtime configuration")
4646
)
4747

4848
// Runtime config is a static copy of the start up config and CfgVars that is used by
@@ -74,23 +74,11 @@ func LoadRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfigSpec, error) {
7474
return migrateLegacyRuntimeConfig(k0sVars, content)
7575
}
7676

77-
config := &RuntimeConfig{}
78-
if err := yaml.Unmarshal(content, config); err != nil {
79-
return nil, err
80-
}
81-
82-
if config.APIVersion != v1beta1.ClusterConfigAPIVersion {
83-
return nil, fmt.Errorf("%w: invalid api version: %s", ErrInvalidRuntimeConfig, config.APIVersion)
84-
}
85-
86-
if config.Kind != RuntimeConfigKind {
87-
return nil, fmt.Errorf("%w: invalid kind: %s", ErrInvalidRuntimeConfig, config.Kind)
77+
config, err := ParseRuntimeConfig(content)
78+
if err != nil {
79+
return nil, fmt.Errorf("failed to parse runtime configuration: %w", err)
8880
}
89-
9081
spec := config.Spec
91-
if spec == nil {
92-
return nil, fmt.Errorf("%w: spec is nil", ErrInvalidRuntimeConfig)
93-
}
9482

9583
// If a pid is defined but there's no process found, the instance of k0s is
9684
// expected to have died, in which case the existing config is removed and
@@ -106,6 +94,28 @@ func LoadRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfigSpec, error) {
10694
return spec, nil
10795
}
10896

97+
func ParseRuntimeConfig(content []byte) (*RuntimeConfig, error) {
98+
var config RuntimeConfig
99+
100+
if err := yaml.Unmarshal(content, &config); err != nil {
101+
return nil, err
102+
}
103+
104+
if config.APIVersion != v1beta1.ClusterConfigAPIVersion {
105+
return nil, fmt.Errorf("%w: invalid api version: %q", ErrInvalidRuntimeConfig, config.APIVersion)
106+
}
107+
108+
if config.Kind != RuntimeConfigKind {
109+
return nil, fmt.Errorf("%w: invalid kind: %q", ErrInvalidRuntimeConfig, config.Kind)
110+
}
111+
112+
if config.Spec == nil {
113+
return nil, fmt.Errorf("%w: spec is nil", ErrInvalidRuntimeConfig)
114+
}
115+
116+
return &config, nil
117+
}
118+
109119
func migrateLegacyRuntimeConfig(k0sVars *CfgVars, content []byte) (*RuntimeConfigSpec, error) {
110120
cfg := &v1beta1.ClusterConfig{}
111121

@@ -136,7 +146,7 @@ func isLegacy(data []byte) bool {
136146
return false
137147
}
138148

139-
func NewRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfigSpec, error) {
149+
func NewRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfig, error) {
140150
if _, err := LoadRuntimeConfig(k0sVars); err == nil {
141151
return nil, ErrK0sAlreadyRunning
142152
}
@@ -179,7 +189,7 @@ func NewRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfigSpec, error) {
179189
return nil, fmt.Errorf("failed to write runtime config: %w", err)
180190
}
181191

182-
return cfg.Spec, nil
192+
return cfg, nil
183193
}
184194

185195
func (r *RuntimeConfigSpec) Cleanup() error {

pkg/config/runtime_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,16 @@ func TestNewRuntimeConfig(t *testing.T) {
110110
}
111111

112112
// create a new runtime config and check if it's valid
113-
spec, err := NewRuntimeConfig(k0sVars)
113+
cfg, err := NewRuntimeConfig(k0sVars)
114+
spec := cfg.Spec
114115
assert.NoError(t, err)
115116
assert.NotNil(t, spec)
116117
assert.Equal(t, tempDir, spec.K0sVars.DataDir)
117118
assert.Equal(t, os.Getpid(), spec.Pid)
118119
assert.NotNil(t, spec.NodeConfig)
119-
cfg, err := spec.K0sVars.NodeConfig()
120+
nodeConfig, err := spec.K0sVars.NodeConfig()
120121
assert.NoError(t, err)
121-
assert.Equal(t, "10.0.0.1", cfg.Spec.API.Address)
122+
assert.Equal(t, "10.0.0.1", nodeConfig.Spec.API.Address)
122123

123124
// try to create a new runtime config when one is already active and check if it returns an error
124125
_, err = NewRuntimeConfig(k0sVars)

pkg/supervisor/supervisor.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23+
"io"
2324
"os"
2425
"os/exec"
2526
"path"
@@ -44,6 +45,7 @@ type Supervisor struct {
4445
BinPath string
4546
RunDir string
4647
DataDir string
48+
Stdin func() io.Reader
4749
Args []string
4850
PidFile string
4951
UID int
@@ -174,6 +176,9 @@ func (s *Supervisor) Supervise() error {
174176
s.cmd = exec.Command(s.BinPath, s.Args...)
175177
s.cmd.Dir = s.DataDir
176178
s.cmd.Env = getEnv(s.DataDir, s.Name, s.KeepEnvPrefix)
179+
if s.Stdin != nil {
180+
s.cmd.Stdin = s.Stdin()
181+
}
177182

178183
// detach from the process group so children don't
179184
// get signals sent directly to parent.

0 commit comments

Comments
 (0)