Skip to content

Commit

Permalink
Merge pull request cert-manager#6345 from inteon/config_cainjector
Browse files Browse the repository at this point in the history
Introduce config file for cainjector options
  • Loading branch information
SgtCoDFish committed Oct 5, 2023
2 parents 3ac37ba + 919f809 commit c56a2fb
Show file tree
Hide file tree
Showing 36 changed files with 2,062 additions and 322 deletions.
162 changes: 162 additions & 0 deletions cmd/cainjector/app/cainjector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
Copyright 2020 The cert-manager Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package app

import (
"context"
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/util/validation/field"

"github.com/cert-manager/cert-manager/cainjector-binary/app/options"
config "github.com/cert-manager/cert-manager/internal/apis/config/cainjector"
cmdutil "github.com/cert-manager/cert-manager/internal/cmd/util"

cainjectorconfigfile "github.com/cert-manager/cert-manager/pkg/cainjector/configfile"
logf "github.com/cert-manager/cert-manager/pkg/logs"
"github.com/cert-manager/cert-manager/pkg/util"
"github.com/cert-manager/cert-manager/pkg/util/configfile"
utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature"
)

const componentController = "cainjector"

func NewCAInjectorCommand(stopCh <-chan struct{}) *cobra.Command {
ctx := cmdutil.ContextWithStopCh(context.Background(), stopCh)
log := logf.Log
ctx = logf.NewContext(ctx, log)

return newCAInjectorCommand(ctx, func(ctx context.Context, cfg *config.CAInjectorConfiguration) error {
return Run(cfg, ctx)
}, os.Args[1:])
}

func newCAInjectorCommand(
ctx context.Context,
run func(context.Context, *config.CAInjectorConfiguration) error,
allArgs []string,
) *cobra.Command {
log := logf.FromContext(ctx, componentController)

cainjectorFlags := options.NewCAInjectorFlags()
cainjectorConfig, err := options.NewCAInjectorConfiguration()
if err != nil {
log.Error(err, "Failed to create new cainjector configuration")
os.Exit(1)
}

cmd := &cobra.Command{
Use: componentController,
Short: fmt.Sprintf("CA Injection Controller for Kubernetes (%s) (%s)", util.AppVersion, util.AppGitCommit),
Long: `
cert-manager CA injector is a Kubernetes addon to automate the injection of CA data into
webhooks and APIServices from cert-manager certificates.
It will ensure that annotated webhooks and API services always have the correct
CA data from the referenced certificates, which can then be used to serve API
servers and webhook servers.`,

RunE: func(cmd *cobra.Command, args []string) error {
if err := loadConfigFromFile(
cmd, allArgs, cainjectorFlags.Config, cainjectorConfig,
func() error {
// set feature gates from initial flags-based config
if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(cainjectorConfig.FeatureGates); err != nil {
return fmt.Errorf("failed to set feature gates from initial flags-based config: %w", err)
}

return nil
},
); err != nil {
return err
}

if err := logf.ValidateAndApplyAsField(&cainjectorConfig.Logging, field.NewPath("logging")); err != nil {
return fmt.Errorf("failed to validate cainjector logging flags: %w", err)
}

return run(ctx, cainjectorConfig)
},
}

cainjectorFlags.AddFlags(cmd.Flags())
options.AddConfigFlags(cmd.Flags(), cainjectorConfig)

// explicitly set provided args in case it does not equal os.Args[:1],
// eg. when running tests
cmd.SetArgs(allArgs)

return cmd
}

// loadConfigFromFile loads the configuration from the provided config file
// path, if one is provided. After loading the config file, the flags are
// re-parsed to ensure that any flags provided to the command line override
// those provided in the config file.
// The newConfigHook is called when the options have been loaded from the
// flags (but not yet the config file) and is re-called after the config file
// has been loaded. This allows us to use the feature flags set by the flags
// while loading the config file.
func loadConfigFromFile(
cmd *cobra.Command,
allArgs []string,
configFilePath string,
cfg *config.CAInjectorConfiguration,
newConfigHook func() error,
) error {
if err := newConfigHook(); err != nil {
return err
}

if len(configFilePath) > 0 {
// compute absolute path based on current working dir
cainjectorConfigFile, err := filepath.Abs(configFilePath)
if err != nil {
return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err)
}

loader, err := configfile.NewConfigurationFSLoader(nil, cainjectorConfigFile)
if err != nil {
return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err)
}

cainjectorConfigFromFile := cainjectorconfigfile.New()
if err := loader.Load(cainjectorConfigFromFile); err != nil {
return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err)
}

cainjectorConfigFromFile.Config.DeepCopyInto(cfg)

_, args, err := cmd.Root().Find(allArgs)
if err != nil {
return fmt.Errorf("failed to re-parse flags: %w", err)
}

if err := cmd.ParseFlags(args); err != nil {
return fmt.Errorf("failed to re-parse flags: %w", err)
}

if err := newConfigHook(); err != nil {
return err
}
}

return nil
}
202 changes: 202 additions & 0 deletions cmd/cainjector/app/cainjector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
Copyright 2021 The cert-manager Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package app

import (
"context"
"fmt"
"io"
"os"
"path"
"reflect"
"testing"

logsapi "k8s.io/component-base/logs/api/v1"

"github.com/cert-manager/cert-manager/cainjector-binary/app/options"
config "github.com/cert-manager/cert-manager/internal/apis/config/cainjector"
logf "github.com/cert-manager/cert-manager/pkg/logs"
)

func testCmdCommand(t *testing.T, tempDir string, yaml string, args func(string) []string) (*config.CAInjectorConfiguration, error) {
var tempFilePath string

func() {
tempFile, err := os.CreateTemp(tempDir, "config-*.yaml")
if err != nil {
t.Error(err)
}
defer tempFile.Close()

tempFilePath = tempFile.Name()

if _, err := tempFile.WriteString(yaml); err != nil {
t.Error(err)
}
}()

var finalConfig *config.CAInjectorConfiguration

logsapi.ResetForTest(nil)
ctx := logf.NewContext(context.TODO(), logf.Log)

cmd := newCAInjectorCommand(ctx, func(ctx context.Context, cc *config.CAInjectorConfiguration) error {
finalConfig = cc
return nil
}, args(tempFilePath))

cmd.SetErr(io.Discard)
cmd.SetOut(io.Discard)

err := cmd.Execute()
return finalConfig, err
}

func TestFlagsAndConfigFile(t *testing.T) {
type testCase struct {
yaml string
args func(string) []string
expError bool
expConfig func(string) *config.CAInjectorConfiguration
}

configFromDefaults := func(
fn func(string, *config.CAInjectorConfiguration),
) func(string) *config.CAInjectorConfiguration {
defaults, err := options.NewCAInjectorConfiguration()
if err != nil {
t.Error(err)
}
return func(tempDir string) *config.CAInjectorConfiguration {
fn(tempDir, defaults)
return defaults
}
}

tests := []testCase{
{
yaml: ``,
args: func(tempFilePath string) []string {
return []string{"--kubeconfig=valid"}
},
expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) {
cc.KubeConfig = "valid"
}),
},
{
yaml: `
apiVersion: cainjector.config.cert-manager.io/v1alpha1
kind: CAInjectorConfiguration
kubeConfig: "<invalid>"
`,
args: func(tempFilePath string) []string {
return []string{"--config=" + tempFilePath, "--kubeconfig=valid"}
},
expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) {
cc.KubeConfig = "valid"
}),
},
{
yaml: `
apiVersion: cainjector.config.cert-manager.io/v1alpha1
kind: CAInjectorConfiguration
kubeConfig: valid
`,
args: func(tempFilePath string) []string {
return []string{"--config=" + tempFilePath}
},
expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) {
cc.KubeConfig = path.Join(tempDir, "valid")
}),
},
{
yaml: `
apiVersion: cainjector.config.cert-manager.io/v1alpha1
kind: CAInjectorConfiguration
enableDataSourceConfig: {}
`,
args: func(tempFilePath string) []string {
return []string{"--config=" + tempFilePath}
},
expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) {
}),
},
{
yaml: `
apiVersion: cainjector.config.cert-manager.io/v1alpha1
kind: CAInjectorConfiguration
enableDataSourceConfig: nil
`,
args: func(tempFilePath string) []string {
return []string{"--config=" + tempFilePath}
},
expError: true,
},
{
yaml: `
apiVersion: cainjector.config.cert-manager.io/v1alpha1
kind: CAInjectorConfiguration
enableInjectableConfig:
validatingWebhookConfigurations: false
`,
args: func(tempFilePath string) []string {
return []string{"--config=" + tempFilePath, "--enable-mutatingwebhookconfigurations-injectable=false"}
},
expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) {
cc.EnableInjectableConfig.ValidatingWebhookConfigurations = false
cc.EnableInjectableConfig.MutatingWebhookConfigurations = false
}),
},
{
yaml: `
apiVersion: cainjector.config.cert-manager.io/v1alpha1
kind: CAInjectorConfiguration
logging:
verbosity: 2
format: text
`,
args: func(tempFilePath string) []string {
return []string{"--config=" + tempFilePath}
},
expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) {
cc.Logging.Verbosity = 2
cc.Logging.Format = "text"
}),
},
}

for i, tc := range tests {
tc := tc
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
tempDir := t.TempDir()

config, err := testCmdCommand(t, tempDir, tc.yaml, tc.args)
if tc.expError != (err != nil) {
if err == nil {
t.Error("expected error, got nil")
} else {
t.Errorf("unexpected error: %v", err)
}
} else if !tc.expError {
expConfig := tc.expConfig(tempDir)
if !reflect.DeepEqual(config, expConfig) {
t.Errorf("expected config %v but got %v", expConfig, config)
}
}
})
}
}
Loading

0 comments on commit c56a2fb

Please sign in to comment.