diff --git a/api/multicluster/v1alpha1/cluster.proto b/api/multicluster/v1alpha1/cluster.proto index 2b5268ec9..4c42f7b02 100644 --- a/api/multicluster/v1alpha1/cluster.proto +++ b/api/multicluster/v1alpha1/cluster.proto @@ -56,4 +56,40 @@ message KubernetesClusterStatus { // List of statuses about the kubernetes cluster. // This list allows for multiple applications/pods to record their connection status. repeated core.skv2.solo.io.Status status = 1; + + // The namespace in which cluster registration resources were created. + string namespace = 2; + + // The set of PolicyRules attached to ClusterRoles when this cluster was registered. + repeated PolicyRule policy_rules = 3; +} + +/* + Copy pasted from the official kubernetes definition: + https://github.com/kubernetes/api/blob/697df40f2d58d7d48b180b83d7b9b2b5ff812923/rbac/v1alpha1/generated.proto#L98 + PolicyRule holds information that describes a policy rule, but does not contain information + about who the rule applies to or which namespace the rule applies to. + */ +message PolicyRule { + // Verbs is a list of Verbs that apply to ALL the ResourceKinds and AttributeRestrictions contained in this rule. VerbAll represents all kinds. + repeated string verbs = 1; + + // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of + // the enumerated resources in any API group will be allowed. + // +optional + repeated string api_groups = 2; + + // Resources is a list of resources this rule applies to. ResourceAll represents all resources. + // +optional + repeated string resources = 3; + + // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // +optional + repeated string resource_names = 4; + + // NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path + // Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. + // Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. + // +optional + repeated string non_resource_urls = 5; } diff --git a/changelog/v0.12.1/kubernetes-cluster-status-settings.yaml b/changelog/v0.12.1/kubernetes-cluster-status-settings.yaml new file mode 100644 index 000000000..5182a285d --- /dev/null +++ b/changelog/v0.12.1/kubernetes-cluster-status-settings.yaml @@ -0,0 +1,4 @@ +changelog: + - type: NEW_FEATURE + description: Extend KubernetesCluster CRD status with namespace and policy rules it was registered with. + issueLink: https://github.com/solo-io/skv2/issues/142 diff --git a/codegen/render/kube_multicluster_test.go b/codegen/render/kube_multicluster_test.go index d7ea107c4..a3964a25a 100644 --- a/codegen/render/kube_multicluster_test.go +++ b/codegen/render/kube_multicluster_test.go @@ -83,22 +83,24 @@ var _ = WithRemoteClusterContextDescribe("Multicluster", func() { remoteCfg := test.ClientConfigWithContext(remoteContext) registrant, err := register.DefaultRegistrant("", "") Expect(err).NotTo(HaveOccurred()) - err = register.RegisterClusterFromConfig(ctx, masterConfig, remoteCfg, register.RbacOptions{ - Options: register.Options{ - ClusterName: cluster2, - Namespace: ns, - RemoteCtx: remoteContext, + err = register.RegisterClusterFromConfig(ctx, masterConfig, remoteCfg, register.Options{ + ClusterName: cluster2, + Namespace: ns, + RemoteNamespace: ns, + RemoteCtx: remoteContext, + RbacOptions: register.RbacOptions{ + ClusterRoleBindings: test.ServiceAccountClusterAdminRoles, }, - ClusterRoleBindings: test.ServiceAccountClusterAdminRoles, }, registrant) Expect(err).NotTo(HaveOccurred()) cfg := test.ClientConfigWithContext("") - err = register.RegisterClusterFromConfig(ctx, masterConfig, cfg, register.RbacOptions{ - Options: register.Options{ - ClusterName: cluster1, - Namespace: ns, + err = register.RegisterClusterFromConfig(ctx, masterConfig, cfg, register.Options{ + ClusterName: cluster1, + Namespace: ns, + RemoteNamespace: ns, + RbacOptions: register.RbacOptions{ + ClusterRoleBindings: test.ServiceAccountClusterAdminRoles, }, - ClusterRoleBindings: test.ServiceAccountClusterAdminRoles, }, registrant) Expect(err).NotTo(HaveOccurred()) }) diff --git a/pkg/api/multicluster.solo.io/v1alpha1/cluster.pb.go b/pkg/api/multicluster.solo.io/v1alpha1/cluster.pb.go index 3b0f39f32..4d81bd1fc 100644 --- a/pkg/api/multicluster.solo.io/v1alpha1/cluster.pb.go +++ b/pkg/api/multicluster.solo.io/v1alpha1/cluster.pb.go @@ -225,10 +225,14 @@ func (m *KubernetesClusterSpec_Eks) GetName() string { type KubernetesClusterStatus struct { // List of statuses about the kubernetes cluster. // This list allows for multiple applications/pods to record their connection status. - Status []*v1.Status `protobuf:"bytes,1,rep,name=status,proto3" json:"status,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Status []*v1.Status `protobuf:"bytes,1,rep,name=status,proto3" json:"status,omitempty"` + // The namespace in which cluster registration resources were created. + Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + // The set of PolicyRules attached to ClusterRoles when this cluster was registered. + PolicyRules []*PolicyRule `protobuf:"bytes,3,rep,name=policy_rules,json=policyRules,proto3" json:"policy_rules,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *KubernetesClusterStatus) Reset() { *m = KubernetesClusterStatus{} } @@ -262,11 +266,113 @@ func (m *KubernetesClusterStatus) GetStatus() []*v1.Status { return nil } +func (m *KubernetesClusterStatus) GetNamespace() string { + if m != nil { + return m.Namespace + } + return "" +} + +func (m *KubernetesClusterStatus) GetPolicyRules() []*PolicyRule { + if m != nil { + return m.PolicyRules + } + return nil +} + +// +//Copy pasted from the official kubernetes definition: +//https://github.com/kubernetes/api/blob/697df40f2d58d7d48b180b83d7b9b2b5ff812923/rbac/v1alpha1/generated.proto#L98 +//PolicyRule holds information that describes a policy rule, but does not contain information +//about who the rule applies to or which namespace the rule applies to. +type PolicyRule struct { + // Verbs is a list of Verbs that apply to ALL the ResourceKinds and AttributeRestrictions contained in this rule. VerbAll represents all kinds. + Verbs []string `protobuf:"bytes,1,rep,name=verbs,proto3" json:"verbs,omitempty"` + // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of + // the enumerated resources in any API group will be allowed. + // +optional + ApiGroups []string `protobuf:"bytes,2,rep,name=api_groups,json=apiGroups,proto3" json:"api_groups,omitempty"` + // Resources is a list of resources this rule applies to. ResourceAll represents all resources. + // +optional + Resources []string `protobuf:"bytes,3,rep,name=resources,proto3" json:"resources,omitempty"` + // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + // +optional + ResourceNames []string `protobuf:"bytes,4,rep,name=resource_names,json=resourceNames,proto3" json:"resource_names,omitempty"` + // NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path + // Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. + // Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. + // +optional + NonResourceUrls []string `protobuf:"bytes,5,rep,name=non_resource_urls,json=nonResourceUrls,proto3" json:"non_resource_urls,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PolicyRule) Reset() { *m = PolicyRule{} } +func (m *PolicyRule) String() string { return proto.CompactTextString(m) } +func (*PolicyRule) ProtoMessage() {} +func (*PolicyRule) Descriptor() ([]byte, []int) { + return fileDescriptor_533baf1d8d7a56a7, []int{2} +} +func (m *PolicyRule) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PolicyRule.Unmarshal(m, b) +} +func (m *PolicyRule) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PolicyRule.Marshal(b, m, deterministic) +} +func (m *PolicyRule) XXX_Merge(src proto.Message) { + xxx_messageInfo_PolicyRule.Merge(m, src) +} +func (m *PolicyRule) XXX_Size() int { + return xxx_messageInfo_PolicyRule.Size(m) +} +func (m *PolicyRule) XXX_DiscardUnknown() { + xxx_messageInfo_PolicyRule.DiscardUnknown(m) +} + +var xxx_messageInfo_PolicyRule proto.InternalMessageInfo + +func (m *PolicyRule) GetVerbs() []string { + if m != nil { + return m.Verbs + } + return nil +} + +func (m *PolicyRule) GetApiGroups() []string { + if m != nil { + return m.ApiGroups + } + return nil +} + +func (m *PolicyRule) GetResources() []string { + if m != nil { + return m.Resources + } + return nil +} + +func (m *PolicyRule) GetResourceNames() []string { + if m != nil { + return m.ResourceNames + } + return nil +} + +func (m *PolicyRule) GetNonResourceUrls() []string { + if m != nil { + return m.NonResourceUrls + } + return nil +} + func init() { proto.RegisterType((*KubernetesClusterSpec)(nil), "multicluster.solo.io.KubernetesClusterSpec") proto.RegisterType((*KubernetesClusterSpec_ProviderInfo)(nil), "multicluster.solo.io.KubernetesClusterSpec.ProviderInfo") proto.RegisterType((*KubernetesClusterSpec_Eks)(nil), "multicluster.solo.io.KubernetesClusterSpec.Eks") proto.RegisterType((*KubernetesClusterStatus)(nil), "multicluster.solo.io.KubernetesClusterStatus") + proto.RegisterType((*PolicyRule)(nil), "multicluster.solo.io.PolicyRule") } func init() { @@ -274,33 +380,41 @@ func init() { } var fileDescriptor_533baf1d8d7a56a7 = []byte{ - // 410 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0x51, 0x8b, 0x13, 0x31, - 0x10, 0xc7, 0x5d, 0xb7, 0x14, 0x3a, 0xbd, 0x13, 0x09, 0x55, 0x6b, 0x45, 0x2d, 0x07, 0x42, 0x5f, - 0x4c, 0x68, 0x7d, 0xf1, 0x45, 0x84, 0x3b, 0x0f, 0x3d, 0x14, 0x91, 0xf5, 0x4d, 0x90, 0x25, 0xdd, - 0xce, 0xed, 0x85, 0xed, 0x66, 0x42, 0x92, 0x5d, 0xce, 0x0f, 0xe1, 0xf7, 0xf0, 0x23, 0xf8, 0x79, - 0xfc, 0x0e, 0xbe, 0xcb, 0x66, 0x63, 0xa9, 0xdc, 0xf9, 0x70, 0x4f, 0x3b, 0xf9, 0xe5, 0xbf, 0x33, - 0xff, 0x99, 0x0c, 0xbc, 0x2e, 0x95, 0xbf, 0x68, 0xd6, 0xbc, 0xa0, 0x5a, 0x38, 0xda, 0xd2, 0x73, - 0x45, 0xc2, 0x55, 0xed, 0x4a, 0x48, 0xa3, 0x44, 0xdd, 0x6c, 0xbd, 0x2a, 0xb6, 0x8d, 0xf3, 0x68, - 0x45, 0xbb, 0x94, 0x5b, 0x73, 0x21, 0x97, 0x22, 0x02, 0x6e, 0x2c, 0x79, 0x62, 0x93, 0x7d, 0x11, - 0xef, 0x52, 0x70, 0x45, 0xb3, 0x49, 0x49, 0x25, 0x05, 0x81, 0xe8, 0xa2, 0x5e, 0x3b, 0x63, 0x78, - 0xe9, 0x7b, 0x88, 0x97, 0x3e, 0xb2, 0x47, 0xbb, 0x6a, 0x05, 0x59, 0x14, 0xed, 0x32, 0x7c, 0xfb, - 0xcb, 0xa3, 0xef, 0x29, 0xdc, 0x7b, 0xdf, 0xac, 0xd1, 0x6a, 0xf4, 0xe8, 0x4e, 0xfa, 0x22, 0x9f, - 0x0d, 0x16, 0xec, 0x29, 0x8c, 0x1d, 0x16, 0x16, 0x7d, 0xae, 0x65, 0x8d, 0xd3, 0x64, 0x9e, 0x2c, - 0x46, 0x19, 0xf4, 0xe8, 0xa3, 0xac, 0x91, 0x3d, 0x83, 0x3b, 0xd1, 0x54, 0xbe, 0xa1, 0x5a, 0x2a, - 0x3d, 0xbd, 0x1d, 0x34, 0x87, 0x91, 0xbe, 0x09, 0x90, 0x7d, 0x85, 0x43, 0x63, 0xa9, 0x55, 0x1b, - 0xb4, 0xb9, 0xd2, 0xe7, 0x34, 0x4d, 0xe7, 0xc9, 0x62, 0xbc, 0x7a, 0xc9, 0xaf, 0x6b, 0x8b, 0x5f, - 0xeb, 0x85, 0x7f, 0x8a, 0x09, 0xce, 0xf4, 0x39, 0x65, 0x07, 0x66, 0xef, 0x34, 0x53, 0x70, 0xb0, - 0x7f, 0xcb, 0x4e, 0x20, 0xc5, 0xca, 0x05, 0xbb, 0xe3, 0x95, 0xb8, 0x49, 0x91, 0xd3, 0xca, 0xbd, - 0xbb, 0x95, 0x75, 0x7f, 0x1f, 0x4f, 0x80, 0xfd, 0xe3, 0x39, 0xf7, 0xdf, 0x0c, 0xce, 0xd6, 0x90, - 0x9e, 0x56, 0x8e, 0xdd, 0x85, 0x54, 0x5a, 0x1d, 0x07, 0xd2, 0x85, 0xec, 0x31, 0x80, 0x2c, 0x0a, - 0x6a, 0xb4, 0xcf, 0xd5, 0x26, 0x4e, 0x61, 0x14, 0xc9, 0xd9, 0x86, 0xdd, 0x87, 0xa1, 0xc5, 0x52, - 0x91, 0x0e, 0xad, 0x8f, 0xb2, 0x78, 0x62, 0x0c, 0x06, 0x61, 0xb4, 0x83, 0x40, 0x43, 0x7c, 0xf4, - 0x01, 0x1e, 0x5c, 0x75, 0xe7, 0xa5, 0x6f, 0x1c, 0x5b, 0xc2, 0xd0, 0x85, 0x68, 0x9a, 0xcc, 0xd3, - 0xc5, 0x78, 0xf5, 0x90, 0x87, 0x77, 0xec, 0x5e, 0x77, 0xd7, 0x59, 0x2f, 0xcd, 0xa2, 0xf0, 0xf8, - 0xed, 0xcf, 0xdf, 0x83, 0xe4, 0xc7, 0xaf, 0x27, 0xc9, 0x97, 0x57, 0xff, 0xdb, 0x42, 0x53, 0x95, - 0x57, 0x36, 0xf1, 0x6f, 0xba, 0xdd, 0x46, 0xae, 0x87, 0x61, 0x5b, 0x5e, 0xfc, 0x09, 0x00, 0x00, - 0xff, 0xff, 0x96, 0x18, 0x77, 0x4b, 0xcd, 0x02, 0x00, 0x00, + // 537 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0xdd, 0x6a, 0x13, 0x41, + 0x14, 0xc7, 0xdd, 0x6e, 0x5a, 0xd8, 0x93, 0xd6, 0x8f, 0x21, 0xea, 0x1a, 0xbf, 0x42, 0x40, 0x08, + 0x82, 0xbb, 0x24, 0xde, 0x78, 0x23, 0x42, 0x63, 0xa9, 0x45, 0x90, 0xb2, 0xe2, 0x8d, 0x20, 0xcb, + 0x64, 0x33, 0xdd, 0x0e, 0xd9, 0xcc, 0x19, 0xe6, 0x23, 0xb4, 0x0f, 0xe1, 0x7b, 0x78, 0x27, 0x78, + 0xe5, 0xf3, 0xf8, 0x0e, 0xde, 0xcb, 0xce, 0x4e, 0xb6, 0x91, 0xc6, 0x0b, 0xaf, 0x72, 0xce, 0xef, + 0xfc, 0x73, 0xe6, 0x3f, 0x67, 0xe7, 0xc0, 0x9b, 0x92, 0x9b, 0x73, 0x3b, 0x4b, 0x0a, 0x5c, 0xa6, + 0x1a, 0x2b, 0x7c, 0xc1, 0x31, 0xd5, 0x8b, 0xd5, 0x24, 0xa5, 0x92, 0xa7, 0x4b, 0x5b, 0x19, 0x5e, + 0x54, 0x56, 0x1b, 0xa6, 0xd2, 0xd5, 0x98, 0x56, 0xf2, 0x9c, 0x8e, 0x53, 0x0f, 0x12, 0xa9, 0xd0, + 0x20, 0xe9, 0x6d, 0x8a, 0x92, 0xba, 0x45, 0xc2, 0xb1, 0xdf, 0x2b, 0xb1, 0x44, 0x27, 0x48, 0xeb, + 0xa8, 0xd1, 0xf6, 0x09, 0xbb, 0x30, 0x0d, 0x64, 0x17, 0xc6, 0xb3, 0x87, 0xed, 0x69, 0x05, 0x2a, + 0x96, 0xae, 0xc6, 0xee, 0xb7, 0x29, 0x0e, 0xbf, 0x86, 0x70, 0xf7, 0xbd, 0x9d, 0x31, 0x25, 0x98, + 0x61, 0x7a, 0xda, 0x1c, 0xf2, 0x51, 0xb2, 0x82, 0x3c, 0x85, 0xae, 0x66, 0x85, 0x62, 0x26, 0x17, + 0x74, 0xc9, 0xe2, 0x60, 0x10, 0x8c, 0xa2, 0x0c, 0x1a, 0xf4, 0x81, 0x2e, 0x19, 0x79, 0x06, 0x37, + 0xbd, 0xa9, 0x7c, 0x8e, 0x4b, 0xca, 0x45, 0xbc, 0xe3, 0x34, 0x07, 0x9e, 0xbe, 0x75, 0x90, 0x7c, + 0x81, 0x03, 0xa9, 0x70, 0xc5, 0xe7, 0x4c, 0xe5, 0x5c, 0x9c, 0x61, 0x1c, 0x0e, 0x82, 0x51, 0x77, + 0xf2, 0x2a, 0xd9, 0x76, 0xad, 0x64, 0xab, 0x97, 0xe4, 0xd4, 0x37, 0x38, 0x11, 0x67, 0x98, 0xed, + 0xcb, 0x8d, 0xac, 0xcf, 0x61, 0x7f, 0xb3, 0x4a, 0xa6, 0x10, 0xb2, 0x85, 0x76, 0x76, 0xbb, 0x93, + 0xf4, 0x7f, 0x0e, 0x39, 0x5a, 0xe8, 0x77, 0x37, 0xb2, 0xfa, 0xdf, 0x87, 0x3d, 0x20, 0x7f, 0x79, + 0xce, 0xcd, 0xa5, 0x64, 0xfd, 0x19, 0x84, 0x47, 0x0b, 0x4d, 0x6e, 0x43, 0x48, 0x95, 0xf0, 0x03, + 0xa9, 0x43, 0xf2, 0x18, 0x80, 0x16, 0x05, 0x5a, 0x61, 0x72, 0x3e, 0xf7, 0x53, 0x88, 0x3c, 0x39, + 0x99, 0x93, 0x7b, 0xb0, 0xa7, 0x58, 0xc9, 0x51, 0xb8, 0xab, 0x47, 0x99, 0xcf, 0x08, 0x81, 0x8e, + 0x1b, 0x6d, 0xc7, 0x51, 0x17, 0x0f, 0xbf, 0x07, 0x70, 0xff, 0xba, 0x3d, 0x43, 0x8d, 0xd5, 0x64, + 0x0c, 0x7b, 0xda, 0x45, 0x71, 0x30, 0x08, 0x47, 0xdd, 0xc9, 0x83, 0xc4, 0x7d, 0xc8, 0xfa, 0xf3, + 0xb6, 0x57, 0x6b, 0xa4, 0x99, 0x17, 0x92, 0x47, 0x10, 0xd5, 0x6d, 0xb5, 0xa4, 0x05, 0x5b, 0x1b, + 0x6b, 0x01, 0x99, 0xc2, 0xbe, 0xc4, 0x8a, 0x17, 0x97, 0xb9, 0xb2, 0x15, 0xd3, 0x71, 0xe8, 0xda, + 0x0e, 0xb6, 0x0f, 0xed, 0xd4, 0x29, 0x33, 0x5b, 0xb1, 0xac, 0x2b, 0xdb, 0x58, 0x0f, 0x7f, 0x04, + 0x00, 0x57, 0x35, 0xd2, 0x83, 0xdd, 0x15, 0x53, 0xb3, 0xc6, 0x63, 0x94, 0x35, 0x89, 0x9b, 0x90, + 0xe4, 0x79, 0xa9, 0xd0, 0x4a, 0x1d, 0xef, 0xb8, 0x52, 0x44, 0x25, 0x3f, 0x76, 0xa0, 0xb6, 0xa9, + 0x98, 0x46, 0xab, 0x0a, 0xef, 0x22, 0xca, 0xae, 0x40, 0xfd, 0xd0, 0xd6, 0x89, 0x7b, 0x8b, 0x3a, + 0xee, 0x38, 0xc9, 0xc1, 0x9a, 0xd6, 0xcf, 0x51, 0x93, 0xe7, 0x70, 0x47, 0xa0, 0xc8, 0x5b, 0xa9, + 0x55, 0x95, 0x8e, 0x77, 0x9d, 0xf2, 0x96, 0x40, 0x91, 0x79, 0xfe, 0x49, 0x55, 0xfa, 0xf0, 0xf8, + 0xe7, 0xef, 0x4e, 0xf0, 0xed, 0xd7, 0x93, 0xe0, 0xf3, 0xeb, 0x7f, 0xad, 0xa7, 0x5c, 0x94, 0xd7, + 0x56, 0x74, 0x3d, 0x8c, 0x76, 0x55, 0x67, 0x7b, 0x6e, 0x8d, 0x5e, 0xfe, 0x09, 0x00, 0x00, 0xff, + 0xff, 0x68, 0xc4, 0x21, 0x30, 0xe6, 0x03, 0x00, 0x00, } func (this *KubernetesClusterSpec) Equal(that interface{}) bool { @@ -456,6 +570,81 @@ func (this *KubernetesClusterStatus) Equal(that interface{}) bool { return false } } + if this.Namespace != that1.Namespace { + return false + } + if len(this.PolicyRules) != len(that1.PolicyRules) { + return false + } + for i := range this.PolicyRules { + if !this.PolicyRules[i].Equal(that1.PolicyRules[i]) { + return false + } + } + if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) { + return false + } + return true +} +func (this *PolicyRule) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*PolicyRule) + if !ok { + that2, ok := that.(PolicyRule) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if len(this.Verbs) != len(that1.Verbs) { + return false + } + for i := range this.Verbs { + if this.Verbs[i] != that1.Verbs[i] { + return false + } + } + if len(this.ApiGroups) != len(that1.ApiGroups) { + return false + } + for i := range this.ApiGroups { + if this.ApiGroups[i] != that1.ApiGroups[i] { + return false + } + } + if len(this.Resources) != len(that1.Resources) { + return false + } + for i := range this.Resources { + if this.Resources[i] != that1.Resources[i] { + return false + } + } + if len(this.ResourceNames) != len(that1.ResourceNames) { + return false + } + for i := range this.ResourceNames { + if this.ResourceNames[i] != that1.ResourceNames[i] { + return false + } + } + if len(this.NonResourceUrls) != len(that1.NonResourceUrls) { + return false + } + for i := range this.NonResourceUrls { + if this.NonResourceUrls[i] != that1.NonResourceUrls[i] { + return false + } + } if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) { return false } diff --git a/pkg/api/multicluster.solo.io/v1alpha1/cluster.pb.hash.go b/pkg/api/multicluster.solo.io/v1alpha1/cluster.pb.hash.go index fb7706551..57074d269 100644 --- a/pkg/api/multicluster.solo.io/v1alpha1/cluster.pb.hash.go +++ b/pkg/api/multicluster.solo.io/v1alpha1/cluster.pb.hash.go @@ -94,6 +94,84 @@ func (m *KubernetesClusterStatus) Hash(hasher hash.Hash64) (uint64, error) { } + if _, err = hasher.Write([]byte(m.GetNamespace())); err != nil { + return 0, err + } + + for _, v := range m.GetPolicyRules() { + + if h, ok := interface{}(v).(safe_hasher.SafeHasher); ok { + if _, err = h.Hash(hasher); err != nil { + return 0, err + } + } else { + if val, err := hashstructure.Hash(v, nil); err != nil { + return 0, err + } else { + if err := binary.Write(hasher, binary.LittleEndian, val); err != nil { + return 0, err + } + } + } + + } + + return hasher.Sum64(), nil +} + +// Hash function +func (m *PolicyRule) Hash(hasher hash.Hash64) (uint64, error) { + if m == nil { + return 0, nil + } + if hasher == nil { + hasher = fnv.New64() + } + var err error + if _, err = hasher.Write([]byte("multicluster.solo.io.github.com/solo-io/skv2/pkg/api/multicluster.solo.io/v1alpha1.PolicyRule")); err != nil { + return 0, err + } + + for _, v := range m.GetVerbs() { + + if _, err = hasher.Write([]byte(v)); err != nil { + return 0, err + } + + } + + for _, v := range m.GetApiGroups() { + + if _, err = hasher.Write([]byte(v)); err != nil { + return 0, err + } + + } + + for _, v := range m.GetResources() { + + if _, err = hasher.Write([]byte(v)); err != nil { + return 0, err + } + + } + + for _, v := range m.GetResourceNames() { + + if _, err = hasher.Write([]byte(v)); err != nil { + return 0, err + } + + } + + for _, v := range m.GetNonResourceUrls() { + + if _, err = hasher.Write([]byte(v)); err != nil { + return 0, err + } + + } + return hasher.Sum64(), nil } diff --git a/pkg/api/multicluster.solo.io/v1alpha1/cluster_json.gen.go b/pkg/api/multicluster.solo.io/v1alpha1/cluster_json.gen.go index dc9455529..95151c7f1 100644 --- a/pkg/api/multicluster.solo.io/v1alpha1/cluster_json.gen.go +++ b/pkg/api/multicluster.solo.io/v1alpha1/cluster_json.gen.go @@ -64,6 +64,17 @@ func (this *KubernetesClusterStatus) UnmarshalJSON(b []byte) error { return ClusterUnmarshaler.Unmarshal(bytes.NewReader(b), this) } +// MarshalJSON is a custom marshaler for PolicyRule +func (this *PolicyRule) MarshalJSON() ([]byte, error) { + str, err := ClusterMarshaler.MarshalToString(this) + return []byte(str), err +} + +// UnmarshalJSON is a custom unmarshaler for PolicyRule +func (this *PolicyRule) UnmarshalJSON(b []byte) error { + return ClusterUnmarshaler.Unmarshal(bytes.NewReader(b), this) +} + var ( ClusterMarshaler = &github_com_gogo_protobuf_jsonpb.Marshaler{} ClusterUnmarshaler = &github_com_gogo_protobuf_jsonpb.Unmarshaler{} diff --git a/pkg/multicluster/kubeconfig/kubeconfig_secret.go b/pkg/multicluster/kubeconfig/kubeconfig_secret.go index 82da22f19..305362dcd 100644 --- a/pkg/multicluster/kubeconfig/kubeconfig_secret.go +++ b/pkg/multicluster/kubeconfig/kubeconfig_secret.go @@ -27,7 +27,7 @@ var ( // TODO settle on how to canonicalize cluster names: https://github.com/solo-io/skv2/issues/15 // ToSecret converts a kubernetes api.Config to a secret with the provided name and namespace. -func ToSecret(namespace string, cluster string, kc api.Config) (*kubev1.Secret, error) { +func ToSecret(namespace string, cluster string, resourceLabels map[string]string, kc api.Config) (*kubev1.Secret, error) { err := convertCertFilesToInline(kc) if err != nil { return nil, err @@ -39,16 +39,17 @@ func ToSecret(namespace string, cluster string, kc api.Config) (*kubev1.Secret, } return &kubev1.Secret{ - ObjectMeta: SecretObjMeta(namespace, cluster), + ObjectMeta: SecretObjMeta(namespace, cluster, resourceLabels), Type: SecretType, Data: map[string][]byte{Key: rawKubeConfig}, }, nil } -func SecretObjMeta(namespace string, cluster string) metav1.ObjectMeta { +func SecretObjMeta(namespace string, cluster string, resourceLabels map[string]string) metav1.ObjectMeta { return metav1.ObjectMeta{ Name: cluster, Namespace: namespace, + Labels: resourceLabels, } } diff --git a/pkg/multicluster/kubeconfig/kubeconfig_secret_test.go b/pkg/multicluster/kubeconfig/kubeconfig_secret_test.go index 7c9d5687d..428dcae6b 100644 --- a/pkg/multicluster/kubeconfig/kubeconfig_secret_test.go +++ b/pkg/multicluster/kubeconfig/kubeconfig_secret_test.go @@ -48,17 +48,21 @@ users: Describe("ToSecret", func() { It("should convert a single KubeConfig to a single secret", func() { + expectedLabels := map[string]string{ + "foo": "bar", + } expectedSecret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: clusterName, Namespace: namespace, + Labels: expectedLabels, }, Data: map[string][]byte{ Key: []byte(kubeConfigRaw), }, Type: SecretType, } - secret, err := ToSecret(namespace, clusterName, *config) + secret, err := ToSecret(namespace, clusterName, expectedLabels, *config) Expect(err).NotTo(HaveOccurred()) Expect(secret).To(Equal(expectedSecret)) }) diff --git a/pkg/multicluster/kubeconfig/loader.go b/pkg/multicluster/kubeconfig/loader.go index 5f1e2066f..d4a43e401 100644 --- a/pkg/multicluster/kubeconfig/loader.go +++ b/pkg/multicluster/kubeconfig/loader.go @@ -1,80 +1,51 @@ package kubeconfig import ( + "fmt" "os" - "time" + "os/user" + "path" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) -// default KubeLoader -func NewKubeLoader(timeout time.Duration) KubeLoader { - return &kubeLoader{ - timeout: timeout.String(), - } -} - -type kubeLoader struct { - timeout string -} - -func (k *kubeLoader) GetClientConfigForContext(path, context string) (clientcmd.ClientConfig, error) { - return k.getConfigWithContext("", path, context) -} - -func (k *kubeLoader) GetRestConfigForContext(path string, context string) (*rest.Config, error) { - cfg, err := k.getConfigWithContext("", path, context) +// Fetch ClientConfig for environment in which this is invoked, override the API Server URL and current context. +// Lifted from https://github.com/kubernetes-sigs/controller-runtime/blob/cb7f85860a8cde7259b35bb84af1fdcb02c098f2/pkg/client/config/config.go#L135 +func GetClientConfigWithContext(apiServerUrl, kubeContext string) (clientcmd.ClientConfig, error) { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + if _, ok := os.LookupEnv("HOME"); !ok { + u, err := user.Current() + if err != nil { + return nil, fmt.Errorf("could not get current user: %v", err) + } + loadingRules.Precedence = append(loadingRules.Precedence, path.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName)) + } + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + loadingRules, + &clientcmd.ConfigOverrides{ + ClusterInfo: clientcmdapi.Cluster{ + Server: apiServerUrl, + }, + CurrentContext: kubeContext, + }), nil +} + +// Fetch rest.Config for environment in which this is invoked, override the API Server URL and current context. +func GetRestConfigWithContext(apiServerUrl, kubeContext string) (*rest.Config, error) { + clientConfig, err := GetClientConfigWithContext(apiServerUrl, kubeContext) if err != nil { return nil, err } - - return cfg.ClientConfig() + return clientConfig.ClientConfig() } -func (k *kubeLoader) GetRestConfigFromBytes(config []byte) (*rest.Config, error) { - clientCfg, err := clientcmd.NewClientConfigFromBytes(config) - if err != nil { - return nil, err - } - return clientCfg.ClientConfig() -} - -func (k *kubeLoader) getConfigWithContext(masterURL, kubeconfigPath, context string) (clientcmd.ClientConfig, error) { - verifiedKubeConfigPath := clientcmd.RecommendedHomeFile - if kubeconfigPath != "" { - verifiedKubeConfigPath = kubeconfigPath - } - - if err := assertKubeConfigExists(verifiedKubeConfigPath); err != nil { - return nil, err - } - - loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - loadingRules.ExplicitPath = verifiedKubeConfigPath - configOverrides := &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterURL}} - - if context != "" { - configOverrides.CurrentContext = context - } - return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides), nil -} - -// expects `path` to be nonempty -func assertKubeConfigExists(path string) error { - if _, err := os.Stat(path); err != nil { - return err - } - - return nil -} - -func (k *kubeLoader) GetRawConfigForContext(path, context string) (clientcmdapi.Config, error) { - cfg, err := k.getConfigWithContext("", path, context) +// Fetch raw Config for environment in which this is invoked, override the API Server URL and current context. +func GetRawConfigWithContext(apiServerUrl, kubeContext string) (clientcmdapi.Config, error) { + clientConfig, err := GetClientConfigWithContext(apiServerUrl, kubeContext) if err != nil { return clientcmdapi.Config{}, err } - - return cfg.RawConfig() + return clientConfig.RawConfig() } diff --git a/pkg/multicluster/register/helpers.go b/pkg/multicluster/register/helpers.go index 3fc36bf8f..f0aeeace0 100644 --- a/pkg/multicluster/register/helpers.go +++ b/pkg/multicluster/register/helpers.go @@ -20,7 +20,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" ) -// Options for registering a cluster +// Options for registering and deregistering a cluster type RegistrationOptions struct { // Management kubeconfig KubeCfg clientcmd.ClientConfig @@ -70,6 +70,13 @@ type RegistrationOptions struct { // List of cluster roles which will be bound to by the created cluster role bindings // The ClusterRoles upserted from the above list will automatically appended to the list ClusterRoleBindings []client.ObjectKey + + // Set of labels to include on the registration output resources, currently consisting of KubernetesCluster and Secret. + ResourceLabels map[string]string + + // Set to true if the remote cluster no longer exists (e.g. was deleted). + // If true, deregistration will not attempt to delete registration resources on the remote cluster. + RemoteClusterDeleted bool } /* @@ -86,21 +93,26 @@ func (opts RegistrationOptions) RegisterCluster( } /* - RegisterCluster is meant to be a helper function to easily "register" a remote cluster. - Currently this entails: - 1. Creating a `ServiceAccount` on the remote cluster. - 2. Binding RBAC `Roles/ClusterRoles` to said `ServiceAccount` - 3. And finally creating a kubeconfig `Secret` with the BearerToken of the remote `ServiceAccount` + RegisterProviderCluster augments RegisterCluster functionality + with additional metadata to persist to the resulting KubernetesCluster object. + ProviderInfo contains cloud provider metadata. */ func (opts RegistrationOptions) RegisterProviderCluster( ctx context.Context, providerInfo *v1alpha1.KubernetesClusterSpec_ProviderInfo, ) error { - masterRestCfg, remoteCfg, rbacOpts, registrant, err := opts.initialize() + masterRestCfg, remoteCfg, registrationOpts, registrant, err := opts.initialize(providerInfo) if err != nil { return err } - return RegisterProviderClusterFromConfig(ctx, masterRestCfg, remoteCfg, rbacOpts, registrant, providerInfo) + + return RegisterClusterFromConfig( + ctx, + masterRestCfg, + remoteCfg, + registrationOpts, + registrant, + ) } /* @@ -113,92 +125,142 @@ func (opts RegistrationOptions) RegisterProviderCluster( func (opts RegistrationOptions) DeregisterCluster( ctx context.Context, ) error { - masterRestCfg, remoteCfg, rbacOpts, registrant, err := opts.initialize() + masterRestCfg, remoteCfg, registrationOpts, registrant, err := opts.initialize(nil) if err != nil { return err } - return DeregisterClusterFromConfig(ctx, masterRestCfg, remoteCfg, rbacOpts, registrant) + return DeregisterClusterFromConfig(ctx, masterRestCfg, remoteCfg, registrationOpts, registrant) } // Initialize registration dependencies -func (opts RegistrationOptions) initialize() (masterRestCfg *rest.Config, remoteCfg clientcmd.ClientConfig, rbacOpts RbacOptions, registrant ClusterRegistrant, err error) { +func (opts RegistrationOptions) initialize( + providerInfo *v1alpha1.KubernetesClusterSpec_ProviderInfo, +) (masterRestCfg *rest.Config, remoteCfg clientcmd.ClientConfig, registrationOpts Options, registrant ClusterRegistrant, err error) { masterRestCfg, err = opts.KubeCfg.ClientConfig() if err != nil { - return masterRestCfg, remoteCfg, rbacOpts, registrant, err + return masterRestCfg, remoteCfg, registrationOpts, registrant, err } remoteCfg = opts.RemoteKubeCfg registrant, err = defaultRegistrant(masterRestCfg, opts.APIServerAddress) if err != nil { - return masterRestCfg, remoteCfg, rbacOpts, registrant, err + return masterRestCfg, remoteCfg, registrationOpts, registrant, err } - rbacOpts = RbacOptions{ - Options: Options{ - ClusterName: opts.ClusterName, - Namespace: opts.Namespace, - RemoteCtx: opts.RemoteCtx, - RemoteNamespace: opts.RemoteNamespace, - ClusterDomain: opts.ClusterDomain, + rolePolicyRules, clusterRolePolicyRules := collectPolicyRules(opts.Roles, opts.ClusterRoles) + + registrationOpts = Options{ + ClusterName: opts.ClusterName, + Namespace: opts.Namespace, + RemoteCtx: opts.RemoteCtx, + RemoteNamespace: opts.RemoteNamespace, + ClusterDomain: opts.ClusterDomain, + RemoteClusterDeleted: opts.RemoteClusterDeleted, + RbacOptions: RbacOptions{ + Roles: opts.Roles, + ClusterRoles: opts.ClusterRoles, + RoleBindings: opts.RoleBindings, + ClusterRoleBindings: opts.ClusterRoleBindings, + }, + RegistrationMetadata: RegistrationMetadata{ + ProviderInfo: providerInfo, + ResourceLabels: opts.ResourceLabels, + RolePolicyRules: rolePolicyRules, + ClusterRolePolicyRules: clusterRolePolicyRules, }, - Roles: opts.Roles, - ClusterRoles: opts.ClusterRoles, - RoleBindings: opts.RoleBindings, - ClusterRoleBindings: opts.ClusterRoleBindings, } - return masterRestCfg, remoteCfg, rbacOpts, registrant, nil + return masterRestCfg, remoteCfg, registrationOpts, registrant, nil } -func RegisterClusterFromConfig( - ctx context.Context, - masterClusterCfg *rest.Config, - remoteCfg clientcmd.ClientConfig, - opts RbacOptions, - registrant ClusterRegistrant, -) error { - return RegisterProviderClusterFromConfig(ctx, masterClusterCfg, remoteCfg, opts, registrant, nil) +// Iterate Roles and ClusterRoles to collect set of PolicyRules for each. +func collectPolicyRules( + roles []*k8s_rbac_types.Role, + clusterRoles []*k8s_rbac_types.ClusterRole, +) (rolePolicyRules []*v1alpha1.PolicyRule, clusterRolePolicyRules []*v1alpha1.PolicyRule) { + for _, role := range roles { + for _, policyRules := range role.Rules { + rolePolicyRules = append(rolePolicyRules, &v1alpha1.PolicyRule{ + Verbs: policyRules.Verbs, + ApiGroups: policyRules.APIGroups, + Resources: policyRules.Resources, + ResourceNames: policyRules.ResourceNames, + NonResourceUrls: policyRules.NonResourceURLs, + }) + } + } + for _, clusterRole := range clusterRoles { + for _, policyRules := range clusterRole.Rules { + clusterRolePolicyRules = append(clusterRolePolicyRules, &v1alpha1.PolicyRule{ + Verbs: policyRules.Verbs, + ApiGroups: policyRules.APIGroups, + Resources: policyRules.Resources, + ResourceNames: policyRules.ResourceNames, + NonResourceUrls: policyRules.NonResourceURLs, + }) + } + } + return rolePolicyRules, clusterRolePolicyRules } -func RegisterProviderClusterFromConfig( +func RegisterClusterFromConfig( ctx context.Context, masterClusterCfg *rest.Config, remoteCfg clientcmd.ClientConfig, - opts RbacOptions, + opts Options, registrant ClusterRegistrant, - providerInfo *v1alpha1.KubernetesClusterSpec_ProviderInfo, ) error { - sa, err := registrant.EnsureRemoteServiceAccount(ctx, remoteCfg, opts.Options) + err := registrant.EnsureRemoteNamespace(ctx, remoteCfg, opts.RemoteNamespace) + if err != nil { + return err + } + + sa, err := registrant.EnsureRemoteServiceAccount(ctx, remoteCfg, opts) if err != nil { return err } - token, err := registrant.CreateRemoteAccessToken(ctx, remoteCfg, client.ObjectKey{ - Namespace: sa.GetNamespace(), - Name: sa.GetName(), - }, opts) + token, err := registrant.CreateRemoteAccessToken( + ctx, + remoteCfg, + client.ObjectKey{ + Namespace: sa.GetNamespace(), + Name: sa.GetName(), + }, + opts) if err != nil { return err } - return registrant.RegisterProviderClusterWithToken(ctx, masterClusterCfg, remoteCfg, token, opts.Options, providerInfo) + return registrant.RegisterClusterWithToken( + ctx, + masterClusterCfg, + remoteCfg, + token, + opts, + ) } func DeregisterClusterFromConfig( ctx context.Context, masterClusterCfg *rest.Config, remoteCfg clientcmd.ClientConfig, - opts RbacOptions, + opts Options, registrant ClusterRegistrant, ) error { var multierr *multierror.Error - if err := registrant.DeregisterCluster(ctx, masterClusterCfg, opts.Options); err != nil { + if err := registrant.DeregisterCluster(ctx, masterClusterCfg, opts); err != nil { multierr = multierror.Append(multierr, err) } - if err := registrant.DeleteRemoteServiceAccount(ctx, remoteCfg, opts.Options); err != nil { + // Do not attempt to delete remote registration resources if remote cluster no longer exists. + if opts.RemoteClusterDeleted { + return multierr.ErrorOrNil() + } + + if err := registrant.DeleteRemoteServiceAccount(ctx, remoteCfg, opts); err != nil { multierr = multierror.Append(multierr, err) } diff --git a/pkg/multicluster/register/interfaces.go b/pkg/multicluster/register/interfaces.go index 7e40dec9a..d559eb10f 100644 --- a/pkg/multicluster/register/interfaces.go +++ b/pkg/multicluster/register/interfaces.go @@ -30,7 +30,7 @@ type Options struct { // If left empty will return error ClusterName string - // Namespace to write namespaced resources to in the "master" and "remote" clusters + // Namespace to write namespaced resources to in the management cluster. // If left empty will return error Namespace string @@ -38,7 +38,7 @@ type Options struct { // We need to explicitly pass this because of this open issue: https://github.com/kubernetes/client-go/issues/735 RemoteCtx string - // Namespace to write namespaced resources to in the "master" and "remote" clusters + // Namespace to write namespaced resources to in the remote cluster. // If left empty will return error RemoteNamespace string @@ -46,11 +46,32 @@ type Options struct { // Defaults to 'cluster.local' // Read more: https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/ ClusterDomain string + + RegistrationMetadata RegistrationMetadata + + RbacOptions RbacOptions + + // Set to true if the remote cluster no longer exists (e.g. was deleted). + // If true, deregistration will not attempt to delete registration resources on the remote cluster. + RemoteClusterDeleted bool } -type RbacOptions struct { - Options +// Optional additional metadata to persist to registration output resources. +type RegistrationMetadata struct { + // Metadata about the provider for cloud hosted k8s clusters. + ProviderInfo *v1alpha1.KubernetesClusterSpec_ProviderInfo + // Labels to add to registration output resources (KubernetesCluster and Secret). + ResourceLabels map[string]string + + // The set of PolicyRules for Roles created on the remote cluster upon registration. + RolePolicyRules []*v1alpha1.PolicyRule + + // The set of PolicyRules for the cluster roles created on the remote cluster upon registration. + ClusterRolePolicyRules []*v1alpha1.PolicyRule +} + +type RbacOptions struct { // A list of roles to bind the New kubeconfig token to // Any Roles in this list will be Upserted by the registrant, prior to binding Roles []*k8s_rbac_types.Role @@ -89,6 +110,14 @@ func (o *Options) validate() error { the registrant instance. */ type ClusterRegistrant interface { + /* + EnsureRemoteNamespace ensures that the specified remoteNamespace exists on the remote cluster being registered. + */ + EnsureRemoteNamespace( + ctx context.Context, + remoteClientCfg clientcmd.ClientConfig, + remoteNamespace string, + ) error /* EnsureRemoteServiceAccount takes an instance of a remote config, and ensure a ServiceAccount exists on the @@ -122,7 +151,7 @@ type ClusterRegistrant interface { ctx context.Context, remoteClientCfg clientcmd.ClientConfig, sa client.ObjectKey, - opts RbacOptions, + opts Options, ) (token string, err error) /* @@ -132,7 +161,7 @@ type ClusterRegistrant interface { DeleteRemoteAccessResources( ctx context.Context, remoteClientCfg clientcmd.ClientConfig, - opts RbacOptions, + opts Options, ) error /* @@ -147,18 +176,6 @@ type ClusterRegistrant interface { opts Options, ) error - /* - Same functionality as RegisterClusterWithToken but supply extra ProviderInfo metadata. - */ - RegisterProviderClusterWithToken( - ctx context.Context, - masterClusterCfg *rest.Config, - remoteClientCfg clientcmd.ClientConfig, - token string, - opts Options, - providerInfo *v1alpha1.KubernetesClusterSpec_ProviderInfo, - ) error - /* DeregisterClusterWithToken deletes all resources created by RegisterClusterWithToken. */ diff --git a/pkg/multicluster/register/mocks/mock_interfaces.go b/pkg/multicluster/register/mocks/mock_interfaces.go index 4893845c8..3aba550e7 100644 --- a/pkg/multicluster/register/mocks/mock_interfaces.go +++ b/pkg/multicluster/register/mocks/mock_interfaces.go @@ -9,7 +9,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - v1alpha1 "github.com/solo-io/skv2/pkg/api/multicluster.solo.io/v1alpha1" register "github.com/solo-io/skv2/pkg/multicluster/register" v1 "k8s.io/api/core/v1" rest "k8s.io/client-go/rest" @@ -40,6 +39,20 @@ func (m *MockClusterRegistrant) EXPECT() *MockClusterRegistrantMockRecorder { return m.recorder } +// EnsureRemoteNamespace mocks base method +func (m *MockClusterRegistrant) EnsureRemoteNamespace(ctx context.Context, remoteClientCfg clientcmd.ClientConfig, remoteNamespace string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureRemoteNamespace", ctx, remoteClientCfg, remoteNamespace) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnsureRemoteNamespace indicates an expected call of EnsureRemoteNamespace +func (mr *MockClusterRegistrantMockRecorder) EnsureRemoteNamespace(ctx, remoteClientCfg, remoteNamespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureRemoteNamespace", reflect.TypeOf((*MockClusterRegistrant)(nil).EnsureRemoteNamespace), ctx, remoteClientCfg, remoteNamespace) +} + // EnsureRemoteServiceAccount mocks base method func (m *MockClusterRegistrant) EnsureRemoteServiceAccount(ctx context.Context, remoteClientCfg clientcmd.ClientConfig, opts register.Options) (*v1.ServiceAccount, error) { m.ctrl.T.Helper() @@ -70,7 +83,7 @@ func (mr *MockClusterRegistrantMockRecorder) DeleteRemoteServiceAccount(ctx, rem } // CreateRemoteAccessToken mocks base method -func (m *MockClusterRegistrant) CreateRemoteAccessToken(ctx context.Context, remoteClientCfg clientcmd.ClientConfig, sa client.ObjectKey, opts register.RbacOptions) (string, error) { +func (m *MockClusterRegistrant) CreateRemoteAccessToken(ctx context.Context, remoteClientCfg clientcmd.ClientConfig, sa client.ObjectKey, opts register.Options) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateRemoteAccessToken", ctx, remoteClientCfg, sa, opts) ret0, _ := ret[0].(string) @@ -85,7 +98,7 @@ func (mr *MockClusterRegistrantMockRecorder) CreateRemoteAccessToken(ctx, remote } // DeleteRemoteAccessResources mocks base method -func (m *MockClusterRegistrant) DeleteRemoteAccessResources(ctx context.Context, remoteClientCfg clientcmd.ClientConfig, opts register.RbacOptions) error { +func (m *MockClusterRegistrant) DeleteRemoteAccessResources(ctx context.Context, remoteClientCfg clientcmd.ClientConfig, opts register.Options) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DeleteRemoteAccessResources", ctx, remoteClientCfg, opts) ret0, _ := ret[0].(error) @@ -112,20 +125,6 @@ func (mr *MockClusterRegistrantMockRecorder) RegisterClusterWithToken(ctx, maste return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterClusterWithToken", reflect.TypeOf((*MockClusterRegistrant)(nil).RegisterClusterWithToken), ctx, masterClusterCfg, remoteClientCfg, token, opts) } -// RegisterProviderClusterWithToken mocks base method -func (m *MockClusterRegistrant) RegisterProviderClusterWithToken(ctx context.Context, masterClusterCfg *rest.Config, remoteClientCfg clientcmd.ClientConfig, token string, opts register.Options, providerInfo *v1alpha1.KubernetesClusterSpec_ProviderInfo) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RegisterProviderClusterWithToken", ctx, masterClusterCfg, remoteClientCfg, token, opts, providerInfo) - ret0, _ := ret[0].(error) - return ret0 -} - -// RegisterProviderClusterWithToken indicates an expected call of RegisterProviderClusterWithToken -func (mr *MockClusterRegistrantMockRecorder) RegisterProviderClusterWithToken(ctx, masterClusterCfg, remoteClientCfg, token, opts, providerInfo interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterProviderClusterWithToken", reflect.TypeOf((*MockClusterRegistrant)(nil).RegisterProviderClusterWithToken), ctx, masterClusterCfg, remoteClientCfg, token, opts, providerInfo) -} - // DeregisterCluster mocks base method func (m *MockClusterRegistrant) DeregisterCluster(ctx context.Context, masterClusterCfg *rest.Config, opts register.Options) error { m.ctrl.T.Helper() diff --git a/pkg/multicluster/register/registrant.go b/pkg/multicluster/register/registrant.go index a05302666..270cb49b8 100644 --- a/pkg/multicluster/register/registrant.go +++ b/pkg/multicluster/register/registrant.go @@ -94,6 +94,18 @@ type clusterRegistrant struct { localAPIServerAddress string } +func (c *clusterRegistrant) EnsureRemoteNamespace( + ctx context.Context, + remoteClientCfg clientcmd.ClientConfig, + remoteNamespace string, +) error { + remoteRestCfg, err := remoteClientCfg.ClientConfig() + if err != nil { + return err + } + return c.ensureRemoteNamespace(ctx, remoteNamespace, remoteRestCfg) +} + func (c *clusterRegistrant) EnsureRemoteServiceAccount( ctx context.Context, remoteClientCfg clientcmd.ClientConfig, @@ -171,7 +183,7 @@ func (c *clusterRegistrant) CreateRemoteAccessToken( ctx context.Context, remoteClientCfg clientcmd.ClientConfig, sa client.ObjectKey, - opts RbacOptions, + opts Options, ) (token string, err error) { if err = (&opts).validate(); err != nil { @@ -188,13 +200,15 @@ func (c *clusterRegistrant) CreateRemoteAccessToken( return "", err } - roleBindings := make([]client.ObjectKey, 0, len(opts.Roles)+len(opts.RoleBindings)) - roleBindings = append(roleBindings, opts.RoleBindings...) - if len(opts.Roles) != 0 { - if err = c.upsertRoles(ctx, remoteCfg, opts.Roles); err != nil { + rbacOpts := opts.RbacOptions + + roleBindings := make([]client.ObjectKey, 0, len(rbacOpts.Roles)+len(rbacOpts.RoleBindings)) + roleBindings = append(roleBindings, rbacOpts.RoleBindings...) + if len(rbacOpts.Roles) != 0 { + if err = c.upsertRoles(ctx, remoteCfg, rbacOpts.Roles); err != nil { return "", err } - for _, v := range opts.Roles { + for _, v := range rbacOpts.Roles { roleBindings = append(roleBindings, client.ObjectKey{ Namespace: v.GetNamespace(), Name: v.GetName(), @@ -207,13 +221,13 @@ func (c *clusterRegistrant) CreateRemoteAccessToken( } } - clusterRoleBindings := make([]client.ObjectKey, 0, len(opts.ClusterRoles)+len(opts.ClusterRoleBindings)) - clusterRoleBindings = append(clusterRoleBindings, opts.ClusterRoleBindings...) - if len(opts.ClusterRoles) != 0 { - if err = c.upsertClusterRoles(ctx, remoteCfg, opts.ClusterRoles); err != nil { + clusterRoleBindings := make([]client.ObjectKey, 0, len(rbacOpts.ClusterRoles)+len(rbacOpts.ClusterRoleBindings)) + clusterRoleBindings = append(clusterRoleBindings, rbacOpts.ClusterRoleBindings...) + if len(rbacOpts.ClusterRoles) != 0 { + if err = c.upsertClusterRoles(ctx, remoteCfg, rbacOpts.ClusterRoles); err != nil { return "", err } - for _, v := range opts.ClusterRoles { + for _, v := range rbacOpts.ClusterRoles { clusterRoleBindings = append(clusterRoleBindings, client.ObjectKey{ Name: v.GetName(), }) @@ -230,22 +244,25 @@ func (c *clusterRegistrant) CreateRemoteAccessToken( func (c *clusterRegistrant) DeleteRemoteAccessResources(ctx context.Context, remoteClientCfg clientcmd.ClientConfig, - opts RbacOptions, + opts Options, ) error { - saObjMeta := serviceAccountObjMeta(opts.Options) + saObjMeta := serviceAccountObjMeta(opts) sa := client.ObjectKey{Name: saObjMeta.Name, Namespace: saObjMeta.Namespace} remoteCfg, err := remoteClientCfg.ClientConfig() if err != nil { return err } + + rbacOpts := opts.RbacOptions + var multierr *multierror.Error // Delete Roles - if err := c.deleteRoles(ctx, remoteCfg, opts.Roles); err != nil { + if err := c.deleteRoles(ctx, remoteCfg, rbacOpts.Roles); err != nil { multierr = multierror.Append(multierr, err) } // Delete ClusterRoles - if err := c.deleteClusterRoles(ctx, remoteCfg, opts.ClusterRoles); err != nil { + if err := c.deleteClusterRoles(ctx, remoteCfg, rbacOpts.ClusterRoles); err != nil { multierr = multierror.Append(multierr, err) } @@ -255,9 +272,9 @@ func (c *clusterRegistrant) DeleteRemoteAccessResources(ctx context.Context, } // Delete RoleBindings - roleBindings := make([]client.ObjectKey, 0, len(opts.Roles)+len(opts.RoleBindings)) - roleBindings = append(roleBindings, opts.RoleBindings...) - for _, v := range opts.Roles { + roleBindings := make([]client.ObjectKey, 0, len(rbacOpts.Roles)+len(rbacOpts.RoleBindings)) + roleBindings = append(roleBindings, rbacOpts.RoleBindings...) + for _, v := range rbacOpts.Roles { roleBindings = append(roleBindings, client.ObjectKey{ Name: v.GetName(), Namespace: v.GetNamespace(), @@ -268,9 +285,9 @@ func (c *clusterRegistrant) DeleteRemoteAccessResources(ctx context.Context, } // Delete ClusterRoleBindings - clusterRoleBindings := make([]client.ObjectKey, 0, len(opts.ClusterRoles)+len(opts.ClusterRoleBindings)) - clusterRoleBindings = append(clusterRoleBindings, opts.ClusterRoleBindings...) - for _, v := range opts.ClusterRoles { + clusterRoleBindings := make([]client.ObjectKey, 0, len(rbacOpts.ClusterRoles)+len(rbacOpts.ClusterRoleBindings)) + clusterRoleBindings = append(clusterRoleBindings, rbacOpts.ClusterRoleBindings...) + for _, v := range rbacOpts.ClusterRoles { clusterRoleBindings = append(clusterRoleBindings, client.ObjectKey{ Name: v.GetName(), }) @@ -288,17 +305,6 @@ func (c *clusterRegistrant) RegisterClusterWithToken( remoteClientCfg clientcmd.ClientConfig, token string, opts Options, -) error { - return c.RegisterProviderClusterWithToken(ctx, masterClusterCfg, remoteClientCfg, token, opts, nil) -} - -func (c *clusterRegistrant) RegisterProviderClusterWithToken( - ctx context.Context, - masterClusterCfg *rest.Config, - remoteClientCfg clientcmd.ClientConfig, - token string, - opts Options, - providerInfo *v1alpha1.KubernetesClusterSpec_ProviderInfo, ) error { if err := (&opts).validate(); err != nil { return err @@ -333,6 +339,7 @@ func (c *clusterRegistrant) RegisterProviderClusterWithToken( kcSecret, err := kubeconfig.ToSecret( opts.Namespace, opts.ClusterName, + opts.RegistrationMetadata.ResourceLabels, c.buildRemoteCfg(remoteCluster, remoteContext, opts.ClusterName, token), ) if err != nil { @@ -343,14 +350,24 @@ func (c *clusterRegistrant) RegisterProviderClusterWithToken( return err } - kubeCluster := buildKubeClusterResource(kcSecret, opts.ClusterDomain, providerInfo) + kubeCluster := buildKubeClusterResource( + kcSecret, + opts.RegistrationMetadata.ResourceLabels, + opts.ClusterDomain, + opts.RegistrationMetadata.ProviderInfo, + opts.RemoteNamespace, + opts.RegistrationMetadata.ClusterRolePolicyRules, + ) kubeClusterClient, err := c.kubeClusterFactory(masterClusterCfg) if err != nil { return err } - return kubeClusterClient.UpsertKubernetesCluster(ctx, kubeCluster) + if err = kubeClusterClient.UpsertKubernetesCluster(ctx, kubeCluster); err != nil { + return err + } + return kubeClusterClient.UpdateKubernetesClusterStatus(ctx, kubeCluster) } func (c *clusterRegistrant) DeregisterCluster( @@ -362,8 +379,8 @@ func (c *clusterRegistrant) DeregisterCluster( return err } - kcSecretObjMeta := kubeconfig.SecretObjMeta(opts.Namespace, opts.ClusterName) - kubeClusterObjMeta := kubeClusterObjMeta(kcSecretObjMeta.Name, kcSecretObjMeta.Namespace) + kcSecretObjMeta := kubeconfig.SecretObjMeta(opts.Namespace, opts.ClusterName, nil) + kubeClusterObjMeta := kubeClusterObjMeta(kcSecretObjMeta.Name, kcSecretObjMeta.Namespace, nil) kubeClusterClient, err := c.kubeClusterFactory(masterClusterCfg) if err != nil { return err @@ -383,26 +400,34 @@ func (c *clusterRegistrant) DeregisterCluster( func buildKubeClusterResource( secret *corev1.Secret, + labels map[string]string, clusterDomain string, providerInfo *v1alpha1.KubernetesClusterSpec_ProviderInfo, + remoteNamespace string, + policyRules []*v1alpha1.PolicyRule, ) *v1alpha1.KubernetesCluster { if clusterDomain == "" { clusterDomain = DefaultClusterDomain } return &v1alpha1.KubernetesCluster{ - ObjectMeta: kubeClusterObjMeta(secret.Name, secret.Namespace), + ObjectMeta: kubeClusterObjMeta(secret.Name, secret.Namespace, labels), Spec: v1alpha1.KubernetesClusterSpec{ SecretName: secret.Name, ClusterDomain: clusterDomain, ProviderInfo: providerInfo, }, + Status: v1alpha1.KubernetesClusterStatus{ + Namespace: remoteNamespace, + PolicyRules: policyRules, + }, } } -func kubeClusterObjMeta(secretName, secretNamespace string) metav1.ObjectMeta { +func kubeClusterObjMeta(secretName, secretNamespace string, labels map[string]string) metav1.ObjectMeta { return metav1.ObjectMeta{ Name: secretName, Namespace: secretNamespace, + Labels: labels, } } diff --git a/pkg/multicluster/register/registrant_test.go b/pkg/multicluster/register/registrant_test.go index 715577f1c..51d3ce4ca 100644 --- a/pkg/multicluster/register/registrant_test.go +++ b/pkg/multicluster/register/registrant_test.go @@ -225,59 +225,59 @@ var _ = Describe("Registrant", func() { ClientConfig(). Return(nil, nil) - opts := register.RbacOptions{ - Options: register.Options{ - ClusterName: clusterName, - Namespace: namespace, - RemoteCtx: remoteCtx, - }, - Roles: []*rbacv1.Role{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "r-1", - Namespace: namespace, + opts := register.Options{ + ClusterName: clusterName, + Namespace: namespace, + RemoteCtx: remoteCtx, + RbacOptions: register.RbacOptions{ + Roles: []*rbacv1.Role{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "r-1", + Namespace: namespace, + }, }, }, - }, - ClusterRoles: []*rbacv1.ClusterRole{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "cr-1", + ClusterRoles: []*rbacv1.ClusterRole{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cr-1", + }, }, }, - }, - RoleBindings: []client.ObjectKey{ - { - Namespace: "namespace", - Name: "rb-1", + RoleBindings: []client.ObjectKey{ + { + Namespace: "namespace", + Name: "rb-1", + }, }, - }, - ClusterRoleBindings: []client.ObjectKey{ - { - Namespace: "", - Name: "crb-1", + ClusterRoleBindings: []client.ObjectKey{ + { + Namespace: "", + Name: "crb-1", + }, }, }, } - + rbacOpts := opts.RbacOptions roleClient.EXPECT(). - UpsertRole(ctx, opts.Roles[0]). + UpsertRole(ctx, rbacOpts.Roles[0]). Return(nil) clusterRBACBinder.EXPECT(). - BindRoles(ctx, sa, append(opts.RoleBindings, client.ObjectKey{ - Name: opts.Roles[0].GetName(), - Namespace: opts.Roles[0].GetNamespace(), + BindRoles(ctx, sa, append(rbacOpts.RoleBindings, client.ObjectKey{ + Name: rbacOpts.Roles[0].GetName(), + Namespace: rbacOpts.Roles[0].GetNamespace(), })). Return(nil) clusterRoleClient.EXPECT(). - UpsertClusterRole(ctx, opts.ClusterRoles[0]). + UpsertClusterRole(ctx, rbacOpts.ClusterRoles[0]). Return(nil) clusterRBACBinder.EXPECT(). - BindClusterRoles(ctx, sa, append(opts.ClusterRoleBindings, client.ObjectKey{ - Name: opts.ClusterRoles[0].GetName(), + BindClusterRoles(ctx, sa, append(rbacOpts.ClusterRoleBindings, client.ObjectKey{ + Name: rbacOpts.ClusterRoles[0].GetName(), })).Return(nil) token := "hello" @@ -345,60 +345,62 @@ var _ = Describe("Registrant", func() { ClientConfig(). Return(nil, nil) - opts := register.RbacOptions{ - Options: register.Options{ - ClusterName: clusterName, - Namespace: namespace, - RemoteCtx: remoteCtx, - RemoteNamespace: "remote-namespace", - }, - Roles: []*rbacv1.Role{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "r-1", - Namespace: namespace, + opts := register.Options{ + ClusterName: clusterName, + Namespace: namespace, + RemoteCtx: remoteCtx, + RemoteNamespace: "remote-namespace", + RbacOptions: register.RbacOptions{ + Roles: []*rbacv1.Role{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "r-1", + Namespace: namespace, + }, }, }, - }, - ClusterRoles: []*rbacv1.ClusterRole{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "cr-1", + ClusterRoles: []*rbacv1.ClusterRole{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cr-1", + }, }, }, - }, - RoleBindings: []client.ObjectKey{ - { - Namespace: "namespace", - Name: "rb-1", + RoleBindings: []client.ObjectKey{ + { + Namespace: "namespace", + Name: "rb-1", + }, }, - }, - ClusterRoleBindings: []client.ObjectKey{ - { - Namespace: "", - Name: "crb-1", + ClusterRoleBindings: []client.ObjectKey{ + { + Namespace: "", + Name: "crb-1", + }, }, }, } + rbacOpts := opts.RbacOptions + roleClient.EXPECT(). - DeleteRole(ctx, client.ObjectKey{Name: opts.Roles[0].Name, Namespace: opts.Roles[0].Namespace}). + DeleteRole(ctx, client.ObjectKey{Name: rbacOpts.Roles[0].Name, Namespace: rbacOpts.Roles[0].Namespace}). Return(nil) clusterRBACBinder.EXPECT(). - DeleteRoleBindings(ctx, sa, append(opts.RoleBindings, client.ObjectKey{ - Name: opts.Roles[0].GetName(), - Namespace: opts.Roles[0].GetNamespace(), + DeleteRoleBindings(ctx, sa, append(rbacOpts.RoleBindings, client.ObjectKey{ + Name: rbacOpts.Roles[0].GetName(), + Namespace: rbacOpts.Roles[0].GetNamespace(), })). Return(nil) clusterRoleClient.EXPECT(). - DeleteClusterRole(ctx, opts.ClusterRoles[0].Name). + DeleteClusterRole(ctx, rbacOpts.ClusterRoles[0].Name). Return(nil) clusterRBACBinder.EXPECT(). - DeleteClusterRoleBindings(ctx, sa, append(opts.ClusterRoleBindings, client.ObjectKey{ - Name: opts.ClusterRoles[0].GetName(), + DeleteClusterRoleBindings(ctx, sa, append(rbacOpts.ClusterRoleBindings, client.ObjectKey{ + Name: rbacOpts.ClusterRoles[0].GetName(), })).Return(nil) err := clusterRegistrant.DeleteRemoteAccessResources(ctx, clientConfig, opts) @@ -476,7 +478,7 @@ var _ = Describe("Registrant", func() { }). Return(nil, errors.NewNotFound(schema.GroupResource{}, "")) - secret, err := kubeconfig.ToSecret(namespace, clusterName, api.Config{ + secret, err := kubeconfig.ToSecret(namespace, clusterName, nil, api.Config{ Kind: "Secret", APIVersion: "kubernetes_core", Preferences: api.Preferences{}, @@ -502,14 +504,18 @@ var _ = Describe("Registrant", func() { CreateSecret(ctx, secret). Return(nil) - kubeClusterClient.EXPECT(). - UpsertKubernetesCluster(ctx, &v1alpha1.KubernetesCluster{ - ObjectMeta: secret.ObjectMeta, - Spec: v1alpha1.KubernetesClusterSpec{ - SecretName: secret.Name, - ClusterDomain: "cluster.local", - }, - }).Return(nil) + kubeCluster := &v1alpha1.KubernetesCluster{ + ObjectMeta: secret.ObjectMeta, + Spec: v1alpha1.KubernetesClusterSpec{ + SecretName: secret.Name, + ClusterDomain: "cluster.local", + }, + Status: v1alpha1.KubernetesClusterStatus{ + Namespace: namespace, + }, + } + kubeClusterClient.EXPECT().UpsertKubernetesCluster(ctx, kubeCluster).Return(nil) + kubeClusterClient.EXPECT().UpdateKubernetesClusterStatus(ctx, kubeCluster).Return(nil) err = clusterRegistrant.RegisterClusterWithToken(ctx, restCfg, clientConfig, token, opts) @@ -530,10 +536,36 @@ var _ = Describe("Registrant", func() { kubeClusterClientFactory, ) + providerInfo := &v1alpha1.KubernetesClusterSpec_ProviderInfo{ + ProviderInfoType: &v1alpha1.KubernetesClusterSpec_ProviderInfo_Eks{ + Eks: &v1alpha1.KubernetesClusterSpec_Eks{ + Arn: "arn", + AccountId: "accountId", + Region: "region", + Name: "name", + }, + }, + } + + policyRules := []*v1alpha1.PolicyRule{ + { + Verbs: []string{"watch"}, + ApiGroups: []string{"v1alpha1"}, + Resources: []string{"foo"}, + }, + } + opts := register.Options{ ClusterName: clusterName, Namespace: namespace, RemoteCtx: remoteCtx, + RegistrationMetadata: register.RegistrationMetadata{ + ProviderInfo: providerInfo, + ResourceLabels: map[string]string{ + "foo": "bar", + }, + ClusterRolePolicyRules: policyRules, + }, } restCfg := &rest.Config{ @@ -580,7 +612,7 @@ var _ = Describe("Registrant", func() { }). Return(nil, errors.NewNotFound(schema.GroupResource{}, "")) - secret, err := kubeconfig.ToSecret(namespace, clusterName, api.Config{ + secret, err := kubeconfig.ToSecret(namespace, clusterName, map[string]string{"foo": "bar"}, api.Config{ Kind: "Secret", APIVersion: "kubernetes_core", Preferences: api.Preferences{}, @@ -606,28 +638,34 @@ var _ = Describe("Registrant", func() { CreateSecret(ctx, secret). Return(nil) - providerInfo := &v1alpha1.KubernetesClusterSpec_ProviderInfo{ - ProviderInfoType: &v1alpha1.KubernetesClusterSpec_ProviderInfo_Eks{ - Eks: &v1alpha1.KubernetesClusterSpec_Eks{ - Arn: "arn", - AccountId: "accountId", - Region: "region", - Name: "name", + kubeCluster := &v1alpha1.KubernetesCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: secret.ObjectMeta.Name, + Namespace: secret.ObjectMeta.Namespace, + Labels: map[string]string{ + "foo": "bar", }, }, + Spec: v1alpha1.KubernetesClusterSpec{ + SecretName: secret.Name, + ClusterDomain: "cluster.local", + ProviderInfo: providerInfo, + }, + Status: v1alpha1.KubernetesClusterStatus{ + Namespace: "namespace", + PolicyRules: policyRules, + }, } - - kubeClusterClient.EXPECT(). - UpsertKubernetesCluster(ctx, &v1alpha1.KubernetesCluster{ - ObjectMeta: secret.ObjectMeta, - Spec: v1alpha1.KubernetesClusterSpec{ - SecretName: secret.Name, - ClusterDomain: "cluster.local", - ProviderInfo: providerInfo, - }, - }).Return(nil) - - err = clusterRegistrant.RegisterProviderClusterWithToken(ctx, restCfg, clientConfig, token, opts, providerInfo) + kubeClusterClient.EXPECT().UpsertKubernetesCluster(ctx, kubeCluster).Return(nil) + kubeClusterClient.EXPECT().UpdateKubernetesClusterStatus(ctx, kubeCluster).Return(nil) + + err = clusterRegistrant.RegisterClusterWithToken( + ctx, + restCfg, + clientConfig, + token, + opts, + ) Expect(err).NotTo(HaveOccurred()) }) @@ -703,7 +741,7 @@ var _ = Describe("Registrant", func() { overwrittenApiConfig.Clusters[clusterName].CertificateAuthority = "" overwrittenApiConfig.Clusters[clusterName].CertificateAuthorityData = []byte("") - secret, err := kubeconfig.ToSecret(namespace, clusterName, api.Config{ + secret, err := kubeconfig.ToSecret(namespace, clusterName, nil, api.Config{ Kind: "Secret", APIVersion: "kubernetes_core", Preferences: api.Preferences{}, @@ -729,14 +767,18 @@ var _ = Describe("Registrant", func() { CreateSecret(ctx, secret). Return(nil) - kubeClusterClient.EXPECT(). - UpsertKubernetesCluster(ctx, &v1alpha1.KubernetesCluster{ - ObjectMeta: secret.ObjectMeta, - Spec: v1alpha1.KubernetesClusterSpec{ - SecretName: secret.Name, - ClusterDomain: "cluster.local", - }, - }).Return(nil) + kubeCluster := &v1alpha1.KubernetesCluster{ + ObjectMeta: secret.ObjectMeta, + Spec: v1alpha1.KubernetesClusterSpec{ + SecretName: secret.Name, + ClusterDomain: "cluster.local", + }, + Status: v1alpha1.KubernetesClusterStatus{ + Namespace: namespace, + }, + } + kubeClusterClient.EXPECT().UpsertKubernetesCluster(ctx, kubeCluster).Return(nil) + kubeClusterClient.EXPECT().UpdateKubernetesClusterStatus(ctx, kubeCluster).Return(nil) err = clusterRegistrant.RegisterClusterWithToken(ctx, restCfg, clientConfig, token, opts)