-
Notifications
You must be signed in to change notification settings - Fork 100
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
Move install routine into common code and add tests. #75
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
Copyright 2020 The Knative 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 common | ||
|
||
import ( | ||
"fmt" | ||
|
||
mf "github.com/manifestival/manifestival" | ||
"knative.dev/operator/pkg/apis/operator/v1alpha1" | ||
) | ||
|
||
var ( | ||
role mf.Predicate = mf.Any(mf.ByKind("ClusterRole"), mf.ByKind("Role")) | ||
rolebinding mf.Predicate = mf.Any(mf.ByKind("ClusterRoleBinding"), mf.ByKind("RoleBinding")) | ||
) | ||
|
||
// Install applies the manifest resources for the given version and updates the given | ||
// status accordingly. | ||
func Install(manifest *mf.Manifest, version string, status v1alpha1.KComponentStatus) error { | ||
// The Operator needs a higher level of permissions if it 'bind's non-existent roles. | ||
// To avoid this, we strictly order the manifest application as (Cluster)Roles, then | ||
// (Cluster)RoleBindings, then the rest of the manifest. | ||
if err := manifest.Filter(role).Apply(); err != nil { | ||
status.MarkInstallFailed(err.Error()) | ||
return fmt.Errorf("failed to apply (cluster)roles: %w", err) | ||
} | ||
if err := manifest.Filter(rolebinding).Apply(); err != nil { | ||
status.MarkInstallFailed(err.Error()) | ||
return fmt.Errorf("failed to apply (cluster)rolebindings: %w", err) | ||
} | ||
if err := manifest.Filter(mf.None(role, rolebinding)).Apply(); err != nil { | ||
status.MarkInstallFailed(err.Error()) | ||
return fmt.Errorf("failed to apply non rbac manifest: %w", err) | ||
} | ||
status.MarkInstallSucceeded() | ||
status.SetVersion(version) | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/* | ||
Copyright 2020 The Knative 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 common | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
mf "github.com/manifestival/manifestival" | ||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"knative.dev/operator/pkg/apis/operator/v1alpha1" | ||
) | ||
|
||
func TestInstall(t *testing.T) { | ||
// Resources in the manifest | ||
version := "v0.14-test" | ||
deployment := NamespacedResource("apps/v1", "Deployment", "test", "test-deployment") | ||
role := NamespacedResource("rbac.authorization.k8s.io/v1", "Role", "test", "test-role") | ||
roleBinding := NamespacedResource("rbac.authorization.k8s.io/v1", "RoleBinding", "test", "test-role-binding") | ||
clusterRole := ClusterScopedResource("rbac.authorization.k8s.io/v1", "ClusterRole", "test-cluster-role") | ||
clusterRoleBinding := ClusterScopedResource("rbac.authorization.k8s.io/v1", "ClusterRoleBinding", "test-cluster-role-binding") | ||
|
||
// Deliberately mixing the order in the manifest. | ||
in := []unstructured.Unstructured{*deployment, *role, *roleBinding, *clusterRole, *clusterRoleBinding} | ||
// Expect things to be applied in order. | ||
want := []*unstructured.Unstructured{role, clusterRole, roleBinding, clusterRoleBinding, deployment} | ||
|
||
client := &fakeClient{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should publish a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not quite sure if that's necessary. A little helper that makes me only implement the functions really under test would be nice maybe. |
||
manifest, err := mf.ManifestFrom(mf.Slice(in), mf.UseClient(client)) | ||
if err != nil { | ||
t.Fatalf("Failed to generate manifest: %v", err) | ||
} | ||
|
||
status := &v1alpha1.KnativeServingStatus{ | ||
Version: "0.13-test", | ||
} | ||
if err := Install(&manifest, version, status); err != nil { | ||
t.Fatalf("Install() = %v, want no error", err) | ||
} | ||
|
||
if !cmp.Equal(client.creates, want) { | ||
t.Fatalf("Unexpected creates: %s", cmp.Diff(client.creates, want)) | ||
} | ||
|
||
condition := status.GetCondition(v1alpha1.InstallSucceeded) | ||
if condition == nil || condition.Status != corev1.ConditionTrue { | ||
t.Fatalf("InstallSucceeded = %v, want %v", condition, corev1.ConditionTrue) | ||
} | ||
|
||
if got, want := status.GetVersion(), version; got != want { | ||
t.Fatalf("GetVersion() = %s, want %s", got, want) | ||
} | ||
} | ||
|
||
func TestInstallError(t *testing.T) { | ||
oldVersion := "v0.13-test" | ||
version := "v0.14-test" | ||
|
||
client := &fakeClient{err: errors.New("test")} | ||
manifest, err := mf.ManifestFrom(mf.Slice([]unstructured.Unstructured{ | ||
*NamespacedResource("apps/v1", "Deployment", "test", "test-deployment"), | ||
}), mf.UseClient(client)) | ||
if err != nil { | ||
t.Fatalf("Failed to generate manifest: %v", err) | ||
} | ||
|
||
status := &v1alpha1.KnativeServingStatus{ | ||
Version: oldVersion, | ||
} | ||
if err := Install(&manifest, version, status); err == nil { | ||
t.Fatalf("Install() = nil, wanted an error") | ||
} | ||
|
||
condition := status.GetCondition(v1alpha1.InstallSucceeded) | ||
if condition == nil || condition.Status != corev1.ConditionFalse { | ||
t.Fatalf("InstallSucceeded = %v, want %v", condition, corev1.ConditionFalse) | ||
} | ||
|
||
if got, want := status.GetVersion(), oldVersion; got != want { | ||
t.Fatalf("GetVersion() = %s, want %s", got, want) | ||
} | ||
} | ||
|
||
type fakeClient struct { | ||
err error | ||
creates []*unstructured.Unstructured | ||
} | ||
|
||
func (f *fakeClient) Get(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { | ||
return nil, f.err | ||
} | ||
|
||
func (f *fakeClient) Delete(obj *unstructured.Unstructured, options ...mf.DeleteOption) error { | ||
return f.err | ||
} | ||
|
||
func (f *fakeClient) Create(obj *unstructured.Unstructured, options ...mf.ApplyOption) error { | ||
obj.SetAnnotations(nil) // Deleting the extra annotation. Irrelevant for the test. | ||
f.creates = append(f.creates, obj) | ||
return f.err | ||
} | ||
|
||
func (f *fakeClient) Update(obj *unstructured.Unstructured, options ...mf.ApplyOption) error { | ||
return f.err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this ever became unwieldy, I might toss a yaml file beneath
testdata/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like to have them available programmatically a ton more I must say. As in this case, I can reuse the same objects to create the "in" and "want" lists and even reuse their memory adresses.