forked from cert-manager/cert-manager
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request cert-manager#6345 from inteon/config_cainjector
Introduce config file for cainjector options
- Loading branch information
Showing
36 changed files
with
2,062 additions
and
322 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.