Skip to content

Commit

Permalink
sonobuoy run automatically detects server version, gen uses latest
Browse files Browse the repository at this point in the history
--kube-conformance-image-version has several options:
auto queries the server version and uses that
latest uses the `latest` tag
a literal version (e.g. v1.10.2) always uses that
  • Loading branch information
liztio committed Jul 11, 2018
1 parent b13a01a commit d7cb99d
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 55 deletions.
22 changes: 15 additions & 7 deletions cmd/sonobuoy/app/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,25 @@ func AddSonobuoyImage(image *string, flags *pflag.FlagSet) {
// AddKubeConformanceImage initialises an image url flag.
func AddKubeConformanceImage(image *string, flags *pflag.FlagSet) {
flags.StringVar(
image, "kube-conformance-image", config.DefaultKubeConformanceImage,
image, "kube-conformance-image", "",
"Container image override for the kube conformance image. Overrides --kube-conformance-image-version.",
)
}

// AddKubeConformanceImageVersion initialises an image version flag.
func AddKubeConformanceImageVersion(imageVersion *string, flags *pflag.FlagSet) {
flags.StringVar(
imageVersion, "kube-conformance-image-version", "auto",
"Use Heptio's KubeConformance image, but override the version. Default is 'auto', which will be set to your cluster's version.",
)
func AddKubeConformanceImageVersion(imageVersion *ConformanceImageVersion, flags *pflag.FlagSet, defaultVersion ConformanceImageVersion) {
help := "Use Heptio's KubeConformance image, but override the version. "
switch defaultVersion {
case ConformanceImageVersionAuto:
help += "Default is 'auto', which will be set to your cluster's version."
case ConformanceImageVersionLatest:
help += "Default is 'latest', which will run the tests for the most recently released Sonobuoy conformance image."
default:
help += fmt.Sprintf("Default is '%s'", defaultVersion)
}

*imageVersion = defaultVersion // default
flags.Var(imageVersion, "kube-conformance-image-version", help)
}

// AddKubeconfigFlag adds a kubeconfig flag to the provided command.
Expand Down Expand Up @@ -155,7 +163,7 @@ func AddRBACModeFlags(mode *RBACMode, flags *pflag.FlagSet, defaultMode RBACMode
flags.Var(
mode, "rbac",
// Doesn't use the map in app.rbacModeMap to preserve order so we can add an explanation for detect.
"Whether to enable rbac on Sonobuoy. Valid modes are Enable, Disable, and Detect (query the server to see whether to enable Sonobuoy).",
"Whether to enable rbac on Sonobuoy. Valid modes are Enable, Disable, and Detect (query the server to see whether to enable RBAC).",
)
}

Expand Down
79 changes: 52 additions & 27 deletions cmd/sonobuoy/app/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,30 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes"

"github.com/heptio/sonobuoy/pkg/client"
"github.com/heptio/sonobuoy/pkg/config"
"github.com/heptio/sonobuoy/pkg/errlog"
)

type genFlags struct {
sonobuoyConfig SonobuoyConfig
mode client.Mode
rbacMode RBACMode
kubecfg Kubeconfig
e2eflags *pflag.FlagSet
namespace string
sonobuoyImage string
kubeConformanceImage string
imagePullPolicy ImagePullPolicy
sonobuoyConfig SonobuoyConfig
mode client.Mode
rbacMode RBACMode
kubecfg Kubeconfig
e2eflags *pflag.FlagSet
namespace string
sonobuoyImage string
kubeConformanceImage string
kubeConformanceImageVersion ConformanceImageVersion
imagePullPolicy ImagePullPolicy
}

var genflags genFlags

func GenFlagSet(cfg *genFlags, rbac RBACMode) *pflag.FlagSet {
func GenFlagSet(cfg *genFlags, rbac RBACMode, version ConformanceImageVersion) *pflag.FlagSet {
genset := pflag.NewFlagSet("generate", pflag.ExitOnError)
AddModeFlag(&cfg.mode, genset)
AddSonobuoyConfigFlag(&cfg.sonobuoyConfig, genset)
Expand All @@ -55,6 +58,7 @@ func GenFlagSet(cfg *genFlags, rbac RBACMode) *pflag.FlagSet {
AddNamespaceFlag(&cfg.namespace, genset)
AddSonobuoyImage(&cfg.sonobuoyImage, genset)
AddKubeConformanceImage(&cfg.kubeConformanceImage, genset)
AddKubeConformanceImageVersion(&cfg.kubeConformanceImageVersion, genset, version)

return genset
}
Expand All @@ -65,14 +69,46 @@ func (g *genFlags) Config() (*client.GenConfig, error) {
return nil, errors.Wrap(err, "could not retrieve E2E config")
}

kubeclient, kubeError := maybeGetClient(&g.kubecfg)

rbacEnabled, err := genflags.rbacMode.Enabled(kubeclient)
if err != nil {
if errors.Cause(err) == ErrRBACNoClient {
return nil, errors.Wrap(err, kubeError.Error())
}
return nil, err
}

var discoveryClient discovery.ServerVersionInterface
var image string

if g.kubeConformanceImage != "" {
image = g.kubeConformanceImage
} else {
if kubeclient != nil {
discoveryClient = kubeclient.DiscoveryClient
}

imageVersion, err := g.kubeConformanceImageVersion.Get(discoveryClient)
if err != nil {
if errors.Cause(err) == ErrImageVersionNoClient {
return nil, errors.Wrap(err, kubeError.Error())
} else {
return nil, err
}
}

image = fmt.Sprintf(config.DefaultKubeConformanceImage, imageVersion)
}

return &client.GenConfig{
E2EConfig: e2ecfg,
Config: GetConfigWithMode(&g.sonobuoyConfig, g.mode),
Image: g.sonobuoyImage,
Namespace: g.namespace,
EnableRBAC: getRBACOrExit(&g.rbacMode, &g.kubecfg),
EnableRBAC: rbacEnabled,
ImagePullPolicy: g.imagePullPolicy.String(),
KubeConformanceImage: g.kubeConformanceImage,
KubeConformanceImage: image,
}, nil
}

Expand All @@ -85,7 +121,7 @@ var GenCommand = &cobra.Command{
}

func init() {
GenCommand.Flags().AddFlagSet(GenFlagSet(&genflags, EnabledRBACMode))
GenCommand.Flags().AddFlagSet(GenFlagSet(&genflags, EnabledRBACMode, ConformanceImageVersionLatest))
RootCmd.AddCommand(GenCommand)
}

Expand All @@ -112,11 +148,8 @@ func genManifest(cmd *cobra.Command, args []string) {
os.Exit(1)
}

// getRBACOrExit is a helper function for working with RBACMode. RBACMode is a bit of a special case
// because it only needs a kubeconfig for detect, otherwise errors from kubeconfig can be ignored.
// This function returns a bool because it os.Exit()s in error cases.
func getRBACOrExit(mode *RBACMode, kubeconfig *Kubeconfig) bool {

// maybeGetClient returns a client if one can be found, and the error attempting to retrieve that client if not.
func maybeGetClient(kubeconfig *Kubeconfig) (*kubernetes.Clientset, error) {
// Usually we don't need a client. But in this case, we _might_ if we're using detect.
// So pass in nil if we get an error, then display the errors from trying to get a client
// if it turns out we needed it.
Expand All @@ -133,13 +166,5 @@ func getRBACOrExit(mode *RBACMode, kubeconfig *Kubeconfig) bool {
kubeError = err
}

rbacEnabled, err := genflags.rbacMode.Enabled(client)
if err != nil {
errlog.LogError(errors.Wrap(err, "couldn't detect RBAC mode."))
if errors.Cause(err) == ErrRBACNoClient {
errlog.LogError(kubeError)
}
os.Exit(1)
}
return rbacEnabled
return client, kubeError
}
51 changes: 33 additions & 18 deletions cmd/sonobuoy/app/imageversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ import (
// ConformanceImageVersion represents the version of a conformance image, or "auto" to detect the version
type ConformanceImageVersion string

var (
//ErrImageVersionNoClient is the error returned when we need a client but didn't get on
ErrImageVersionNoClient = errors.New(`can't use nil client with "auto" image version`)
)

const (
// ConformanceImageVersionAuto represents detecting the server's kubernetes version.
ConformanceImageVersionAuto = "auto"
Expand All @@ -42,40 +47,50 @@ func (c *ConformanceImageVersion) Type() string { return "ConformanceImageVersio

// Set the ImageVersion to either the string "auto" or a version string
func (c *ConformanceImageVersion) Set(str string) error {
if str == ConformanceImageVersionAuto {
switch str {
case ConformanceImageVersionAuto:
*c = ConformanceImageVersionAuto
return nil
} else if str == ConformanceImageVersionLatest {
case ConformanceImageVersionLatest:
*c = ConformanceImageVersionLatest
return nil
}

version, err := version.NewVersion(str)
if err != nil {
return err
}

if version.Metadata() != "" || version.Prerelease() != "" {
return errors.New("version cannot have prelease or metadata")
}

if !strings.HasPrefix(str, "v") {
return errors.New("version must start with v")
default:
if err := validateVersion(str); err != nil {
return err
}
*c = ConformanceImageVersion(str)
}

*c = ConformanceImageVersion(str)
return nil
}

// Get retrieves the preset version if there is one, or queries client if the ConformanceImageVersion is set to `auto`.
// kubernetes.Interface.Discovery() provides ServerVersionInterface.
func (c *ConformanceImageVersion) Get(client discovery.ServerVersionInterface) (string, error) {
if *c == ConformanceImageVersionAuto {
if client == nil {
return "", ErrImageVersionNoClient
}
version, err := client.ServerVersion()
if err != nil {
return "", errors.Wrap(err, "couldn't retrieve server version")
}

if err := validateVersion(version.GitVersion); err != nil {
return "", err
}

return version.GitVersion, nil
}
return string(*c), nil
}

func validateVersion(v string) error {
version, err := version.NewVersion(v)
if err == nil {
if version.Metadata() != "" || version.Prerelease() != "" {
err = errors.New("version cannot have prelease or metadata, please use a stable version")
} else if !strings.HasPrefix(v, "v") {
err = errors.New("version must start with v")
}
}
return errors.Wrapf(err, "version %q is invalid", v)
}
21 changes: 20 additions & 1 deletion cmd/sonobuoy/app/imageversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery"
)

func TestSetConformanceImageVersion(t *testing.T) {
Expand Down Expand Up @@ -87,14 +88,20 @@ func TestGetConformanceImageVersion(t *testing.T) {
},
}

betaServerVersion := &fakeServerVersionInterface{
version: version.Info{
GitVersion: "v1.11.0-beta.2.78+e0b33dbc2bde88",
},
}

brokenServerVersion := &fakeServerVersionInterface{
err: errors.New("can't connect"),
}

tests := []struct {
name string
version ConformanceImageVersion
serverVersion *fakeServerVersionInterface
serverVersion discovery.ServerVersionInterface
expected string
error bool
}{
Expand All @@ -110,6 +117,12 @@ func TestGetConformanceImageVersion(t *testing.T) {
serverVersion: brokenServerVersion,
error: true,
},
{
name: "beta server version throws error",
version: "auto",
serverVersion: betaServerVersion,
error: true,
},
{
name: "set version ignores server version",
version: "v1.10.2",
Expand All @@ -134,6 +147,12 @@ func TestGetConformanceImageVersion(t *testing.T) {
serverVersion: brokenServerVersion,
expected: "latest",
},
{
name: "nil serverVersion",
version: "auto",
serverVersion: nil,
error: true,
},
}

for _, test := range tests {
Expand Down
2 changes: 1 addition & 1 deletion cmd/sonobuoy/app/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ var runflags runFlags
func RunFlagSet(cfg *runFlags) *pflag.FlagSet {
runset := pflag.NewFlagSet("run", pflag.ExitOnError)
// Default to detect since we need kubeconfig regardless
runset.AddFlagSet(GenFlagSet(&cfg.genFlags, DetectRBACMode))
runset.AddFlagSet(GenFlagSet(&cfg.genFlags, DetectRBACMode, ConformanceImageVersionAuto))
AddSkipPreflightFlag(&cfg.skipPreflight, runset)
return runset
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const (
// DefaultNamespace is the namespace where the master and plugin workers will run (but not necessarily the pods created by the plugin workers).
DefaultNamespace = "heptio-sonobuoy"
// DefaultKubeConformanceImage is the URL of the docker image to run for the kube conformance tests.
DefaultKubeConformanceImage = "gcr.io/heptio-images/kube-conformance:latest"
DefaultKubeConformanceImage = "gcr.io/heptio-images/kube-conformance:%s"
// DefaultAggregationServerBindPort is the default port for the aggregation server to bind to.
DefaultAggregationServerBindPort = 8080
// DefaultAggregationServerBindAddress is the default address for the aggregation server to bind to.
Expand Down

0 comments on commit d7cb99d

Please sign in to comment.