Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect different versions of cert-manager #1546

Merged
merged 9 commits into from Jun 4, 2020
3 changes: 2 additions & 1 deletion pkg/engine/health/health.go
Expand Up @@ -15,6 +15,7 @@ import (

kudov1beta1 "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1"
"github.com/kudobuilder/kudo/pkg/engine"
"github.com/kudobuilder/kudo/pkg/kudoctl/clog"
)

func isJobTerminallyFailed(job *batchv1.Job) (bool, string) {
Expand Down Expand Up @@ -61,7 +62,7 @@ func IsHealthy(obj runtime.Object) error {
log.Printf("HealthUtil: Deployment %v is NOT healthy. %s", obj.Name, msg)
return errors.New(msg)
}
log.Printf("Deployment %v is marked healthy\n", obj.Name)
clog.V(2).Printf("Deployment %v is marked healthy\n", obj.Name)
return nil
case *batchv1.Job:

Expand Down
76 changes: 42 additions & 34 deletions pkg/kudoctl/cmd/init.go
Expand Up @@ -60,6 +60,7 @@ and finishes with success if KUDO is already installed.

type initCmd struct {
out io.Writer
errOut io.Writer
fs afero.Fs
image string
imagePullPolicy string
Expand All @@ -77,8 +78,8 @@ type initCmd struct {
selfSignedWebhookCA bool
}

func newInitCmd(fs afero.Fs, out io.Writer) *cobra.Command {
i := &initCmd{fs: fs, out: out}
func newInitCmd(fs afero.Fs, out io.Writer, errOut io.Writer, client *kube.Client) *cobra.Command {
i := &initCmd{fs: fs, out: out, errOut: errOut, client: client}

cmd := &cobra.Command{
Use: "init",
Expand Down Expand Up @@ -154,47 +155,54 @@ func (initCmd *initCmd) run() error {
}
}

//TODO: implement output=yaml|json (define a type for output to constrain)
//define an Encoder to replace YAMLWriter
if strings.ToLower(initCmd.output) == "yaml" {
manifests, err := setup.AsYamlManifests(opts, initCmd.crdOnly)
if err != nil {
return err
}
if err := initCmd.YAMLWriter(initCmd.out, manifests); err != nil {
return err
installer := setup.NewInstaller(opts, initCmd.crdOnly)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't know why crdOnly is not part of the opts. But that's another story


// initialize client
if !initCmd.dryRun {
if err := initCmd.initialize(); err != nil {
return clog.Errorf("error initializing: %s", err)
}
}

if initCmd.dryRun {
return nil
// if output is yaml | json, we only print the requested output style.
if initCmd.output == "" {
clog.Printf("$KUDO_HOME has been configured at %s", Settings.Home)
}

// initialize client
if err := initCmd.initialize(); err != nil {
return clog.Errorf("error initializing: %s", err)
if initCmd.clientOnly {
return nil
}
clog.Printf("$KUDO_HOME has been configured at %s", Settings.Home)

// initialize server
if !initCmd.clientOnly {
clog.V(4).Printf("initializing server")
if initCmd.client == nil {
client, err := kube.GetKubeClient(Settings.KubeConfig)
if err != nil {
return clog.Errorf("could not get Kubernetes client: %s", err)
}
initCmd.client = client
clog.V(4).Printf("initializing server")
if initCmd.client == nil {
client, err := kube.GetKubeClient(Settings.KubeConfig)
if err != nil {
return clog.Errorf("could not get Kubernetes client: %s", err)
}
ok, err := initCmd.preInstallVerify(opts)
initCmd.client = client
}
ok, err := initCmd.preInstallVerify(installer)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("failed to verify installation requirements")
}

//TODO: implement output=yaml|json (define a type for output to constrain)
//define an Encoder to replace YAMLWriter
if strings.ToLower(initCmd.output) == "yaml" {
manifests, err := installer.AsYamlManifests()
if err != nil {
return err
}
if !ok {
return fmt.Errorf("failed to verify installation requirements")
if err := initCmd.YAMLWriter(initCmd.out, manifests); err != nil {
return err
}
}

if err := setup.Install(initCmd.client, opts, initCmd.crdOnly); err != nil {
if !initCmd.dryRun {
if err := installer.Install(initCmd.client); err != nil {
return clog.Errorf("error installing: %s", err)
}

Expand All @@ -211,14 +219,14 @@ func (initCmd *initCmd) run() error {
}

// preInstallVerify runs the pre-installation verification and returns true if the installation can continue
func (initCmd *initCmd) preInstallVerify(opts kudoinit.Options) (bool, error) {
func (initCmd *initCmd) preInstallVerify(v kudoinit.InstallVerifier) (bool, error) {
result := verifier.NewResult()
if err := setup.PreInstallVerify(initCmd.client, opts, initCmd.crdOnly, &result); err != nil {
if err := v.PreInstallVerify(initCmd.client, &result); err != nil {
return false, err
}
result.PrintWarnings(initCmd.out)
result.PrintWarnings(initCmd.errOut)
if !result.IsValid() {
result.PrintErrors(initCmd.out)
result.PrintErrors(initCmd.errOut)
return false, nil
}
return true, nil
Expand Down
12 changes: 10 additions & 2 deletions pkg/kudoctl/cmd/init_integration_test.go
Expand Up @@ -104,8 +104,10 @@ func TestIntegInitForCRDs(t *testing.T) {
crds := crd.NewInitializer().Resources()

var buf bytes.Buffer
var errBuf bytes.Buffer
cmd := &initCmd{
out: &buf,
errOut: &errBuf,
fs: afero.NewMemMapFs(),
client: kclient,
crdOnly: true,
Expand Down Expand Up @@ -146,8 +148,10 @@ func TestIntegInitWithNameSpace(t *testing.T) {
crds := crd.NewInitializer().Resources()

var buf bytes.Buffer
var errBuf bytes.Buffer
cmd := &initCmd{
out: &buf,
errOut: &errBuf,
fs: afero.NewMemMapFs(),
client: kclient,
ns: namespace,
Expand All @@ -158,7 +162,7 @@ func TestIntegInitWithNameSpace(t *testing.T) {
err = cmd.run()
require.Error(t, err)
assert.Equal(t, "failed to verify installation requirements", err.Error())
assertStringContains(t, "Namespace integration-test does not exist - KUDO expects that any namespace except the default kudo-system is created beforehand", buf.String())
assertStringContains(t, "Namespace integration-test does not exist - KUDO expects that any namespace except the default kudo-system is created beforehand", errBuf.String())

// Then we manually create the namespace.
ns := testutils.NewResource("v1", "Namespace", namespace, "")
Expand Down Expand Up @@ -269,8 +273,10 @@ func TestInitWithServiceAccount(t *testing.T) {
crds := crd.NewInitializer().Resources()

var buf bytes.Buffer
var errBuf bytes.Buffer
cmd := &initCmd{
out: &buf,
errOut: &errBuf,
fs: afero.NewMemMapFs(),
client: kclient,
ns: namespace,
Expand Down Expand Up @@ -309,7 +315,7 @@ func TestInitWithServiceAccount(t *testing.T) {
if tt.errMessageContains != "" {
require.Error(t, err)
assert.Equal(t, "failed to verify installation requirements", err.Error())
assertStringContains(t, tt.errMessageContains, buf.String())
assertStringContains(t, tt.errMessageContains, errBuf.String())
} else {
assert.NoError(t, err)
defer func() {
Expand Down Expand Up @@ -355,8 +361,10 @@ func TestNoErrorOnReInit(t *testing.T) {
clog.InitNoFlag(&buf, clog.Level(4))
defer func() { clog.InitNoFlag(&buf, clog.Level(0)) }()

var errBuf bytes.Buffer
cmd := &initCmd{
out: &buf,
errOut: &errBuf,
fs: afero.NewMemMapFs(),
client: kclient,
crdOnly: true,
Expand Down
121 changes: 94 additions & 27 deletions pkg/kudoctl/cmd/init_test.go
Expand Up @@ -11,8 +11,11 @@ import (

"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/thoas/go-funk"
v1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
rbacv1 "k8s.io/api/rbac/v1"
extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -29,24 +32,6 @@ import (

var updateGolden = flag.Bool("update", false, "update .golden files and manifests in /config/crd")

func TestInitCmd_dry(t *testing.T) {

var buf bytes.Buffer

cmd := &initCmd{
out: &buf,
fs: afero.NewMemMapFs(),
dryRun: true,
}
if err := cmd.run(); err != nil {
t.Errorf("expected error: %v", err)
}
expected := ""
if !strings.Contains(buf.String(), expected) {
t.Errorf("expected %q, got %q", expected, buf.String())
}
}

func TestInitCmd_exists(t *testing.T) {

var buf bytes.Buffer
Expand Down Expand Up @@ -82,24 +67,36 @@ func TestInitCmd_exists(t *testing.T) {

// TestInitCmd_output tests that init -o can be decoded
func TestInitCmd_output(t *testing.T) {

fc := fake.NewSimpleClientset()
client := &kube.Client{
KubeClient: fc,
ExtClient: apiextfake.NewSimpleClientset(),
}

MockCRD(client, "certificates.cert-manager.io", "v1alpha2")
MockCRD(client, "issuers.cert-manager.io", "v1alpha2")

tests := []string{"yaml"}
for _, s := range tests {
var buf bytes.Buffer
var errOut bytes.Buffer
cmd := &initCmd{
out: &buf,
client: &kube.Client{KubeClient: fc},
errOut: &errOut,
client: client,
output: s,
dryRun: true,
}
// ensure that we can marshal
if err := cmd.run(); err != nil {
t.Fatal(err)
}
// ensure no calls against the server
if got := len(fc.Actions()); got != 0 {
t.Errorf("expected no server calls, got %d", got)
// ensure no modifying calls against the server
forbiddenVerbs := []string{"create", "update", "patch", "delete"}
for _, a := range fc.Actions() {
if funk.Contains(forbiddenVerbs, a.GetVerb()) {
t.Errorf("got modifying server call: %v", a)
}
}

assert.True(t, len(buf.Bytes()) > 0, "Buffer needs to have an output")
Expand All @@ -119,6 +116,44 @@ func TestInitCmd_output(t *testing.T) {
}

func TestInitCmd_yamlOutput(t *testing.T) {
customNs := &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
}
customSa := &v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Namespace: "foo",
Name: "safoo",
},
}
crb := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "safoocrb",
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Namespace: "foo",
Name: "safoo",
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "cluster-admin",
},
}

fc := fake.NewSimpleClientset(crb, customNs, customSa)
client := &kube.Client{
KubeClient: fc,
ExtClient: apiextfake.NewSimpleClientset(),
}

MockCRD(client, "certificates.cert-manager.io", "v1alpha2")
MockCRD(client, "issuers.cert-manager.io", "v1alpha2")

tests := []struct {
name string
goldenFile string
Expand All @@ -127,13 +162,14 @@ func TestInitCmd_yamlOutput(t *testing.T) {
{"custom namespace", "deploy-kudo-ns.yaml", map[string]string{"dry-run": "true", "output": "yaml", "namespace": "foo"}},
{"yaml output", "deploy-kudo.yaml", map[string]string{"dry-run": "true", "output": "yaml"}},
{"service account", "deploy-kudo-sa.yaml", map[string]string{"dry-run": "true", "output": "yaml", "service-account": "safoo", "namespace": "foo"}},
{"using default cert-manager", "deploy-kudo-webhook.yaml", map[string]string{"dry-run": "true", "output": "yaml"}},
}

for _, tt := range tests {
fs := afero.NewMemMapFs()
out := &bytes.Buffer{}
initCmd := newInitCmd(fs, out)
errOut := &bytes.Buffer{}
initCmd := newInitCmd(fs, out, errOut, client)

Settings.AddFlags(initCmd.Flags())

for f, value := range tt.flags {
Expand All @@ -143,7 +179,7 @@ func TestInitCmd_yamlOutput(t *testing.T) {
}

if err := initCmd.RunE(initCmd, []string{}); err != nil {
t.Fatal(err)
t.Fatal(err, errOut.String())
}

gp := filepath.Join("testdata", tt.goldenFile+".golden")
Expand Down Expand Up @@ -183,7 +219,8 @@ func TestNewInitCmd(t *testing.T) {

t.Run(tt.name, func(t *testing.T) {
out := &bytes.Buffer{}
initCmd := newInitCmd(fs, out)
errOut := &bytes.Buffer{}
initCmd := newInitCmd(fs, out, errOut, nil)
for key, value := range tt.flags {
if err := initCmd.Flags().Set(key, value); err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -236,3 +273,33 @@ func TestClientInitialize(t *testing.T) {
}
assert.Equal(t, r.CurrentConfiguration().URL, RepositoryURL)
}

func MockCRD(client *kube.Client, crdName string, apiVersion string) {
client.ExtClient.(*apiextfake.Clientset).Fake.PrependReactor("get", "customresourcedefinitions", func(action testcore.Action) (handled bool, ret runtime.Object, err error) {

getAction, _ := action.(testcore.GetAction)
if getAction != nil {
if getAction.GetName() == crdName {
crd := &extv1.CustomResourceDefinition{
TypeMeta: metav1.TypeMeta{
APIVersion: apiVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: crdName,
},
Spec: extv1.CustomResourceDefinitionSpec{
Versions: []extv1.CustomResourceDefinitionVersion{
{
Name: apiVersion,
},
},
},
Status: extv1.CustomResourceDefinitionStatus{},
}
return true, crd, nil
}
}

return false, nil, nil
})
}