improve IAM module

Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2020-05-22 09:35:05 +08:00
parent 0d12529051
commit 8f93266ec0
640 changed files with 50221 additions and 18179 deletions

View File

@@ -0,0 +1,26 @@
/*
Copyright 2019 The KubeSphere 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 apis
import (
tenantv1alpha2 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha2"
)
func init() {
// Register the types with the Scheme so the components can map objects to GroupVersionKinds and back
AddToSchemes = append(AddToSchemes, tenantv1alpha2.SchemeBuilder.AddToScheme)
}

View File

@@ -28,6 +28,12 @@ const (
ResourceKindGlobalRoleBinding = "GlobalRoleBinding"
ResourcesSingularGlobalRoleBinding = "globalrolebinding"
ResourcesPluralGlobalRoleBinding = "globalrolebindings"
ResourceKindClusterRoleBinding = "ClusterRoleBinding"
ResourcesSingularClusterRoleBinding = "clusterrolebinding"
ResourcesPluralClusterRoleBinding = "clusterrolebindings"
ResourceKindRoleBinding = "RoleBinding"
ResourcesSingularRoleBinding = "rolebinding"
ResourcesPluralRoleBinding = "rolebindings"
ResourceKindGlobalRole = "GlobalRole"
ResourcesSingularGlobalRole = "globalrole"
ResourcesPluralGlobalRole = "globalroles"
@@ -44,10 +50,23 @@ const (
ResourcesSingularRole = "role"
ResourcesPluralRole = "roles"
RegoOverrideAnnotation = "iam.kubesphere.io/rego-override"
GlobalScope = "Global"
ClusterScope = "Cluster"
WorkspaceScope = "Workspace"
NamespaceScope = "Namespace"
AggregationRolesAnnotation = "iam.kubesphere.io/aggregation-roles"
GlobalRoleAnnotation = "iam.kubesphere.io/globalrole"
WorkspaceRoleAnnotation = "iam.kubesphere.io/workspacerole"
ClusterRoleAnnotation = "iam.kubesphere.io/clusterrole"
RoleAnnotation = "iam.kubesphere.io/role"
RoleTemplateLabel = "iam.kubesphere.io/role-template"
UserReferenceLabel = "iam.kubesphere.io/user-ref"
IdentifyProviderLabel = "iam.kubesphere.io/identify-provider"
PasswordEncryptedAnnotation = "iam.kubesphere.io/password-encrypted"
FieldEmail = "email"
AggregateTo = "aggregateTo"
ScopeWorkspace = "workspace"
ScopeCluster = "cluster"
ScopeNamespace = "namespace"
LocalCluster = "local"
GlobalAdmin = "global-admin"
ClusterAdmin = "cluster-admin"
)
// +genclient
@@ -74,7 +93,7 @@ type FinalizerName string
// UserSpec defines the desired state of User
type UserSpec struct {
// Unique email address.
// Unique email address(https://www.ietf.org/rfc/rfc5322.txt).
Email string `json:"email"`
// The preferred written or spoken language for the user.
// +optional
@@ -87,10 +106,7 @@ type UserSpec struct {
// +optional
Groups []string `json:"groups,omitempty"`
// password will be encrypted by mutating admission webhook
EncryptedPassword string `json:"password"`
// Finalizers is an opaque list of values that must be empty to permanently remove object from storage.
// +optional
Finalizers []FinalizerName `json:"finalizers,omitempty"`
EncryptedPassword string `json:"password,omitempty"`
}
type UserState string
@@ -108,16 +124,13 @@ type UserStatus struct {
// The user status
// +optional
State UserState `json:"state,omitempty"`
// Represents the latest available observations of a namespace's current state.
// Represents the latest available observations of a user's current state.
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
Conditions []UserCondition `json:"conditions,omitempty"`
}
type UserCondition struct {
// Type of namespace controller condition.
// Type of user controller condition.
Type UserConditionType `json:"type"`
// Status of the condition, one of True, False, Unknown.
Status ConditionStatus `json:"status"`
@@ -170,21 +183,9 @@ type GlobalRole struct {
// +optional
metav1.ObjectMeta `json:"metadata,omitempty"`
// Rules holds all the PolicyRules for this ClusterRole
Rules []rbacv1.PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"`
// AggregationRule is an optional field that describes how to build the Rules for this GlobalRole.
// If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be
// stomped by the controller.
AggregationRule *AggregationRule `json:"aggregationRule,omitempty" protobuf:"bytes,3,opt,name=aggregationRule"`
}
// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole
type AggregationRule struct {
// ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules.
// If any of the selectors match, then the ClusterRole's permissions will be added
// Rules holds all the PolicyRules for this GlobalRole
// +optional
RoleSelectors []metav1.LabelSelector `json:"roleSelectors,omitempty" protobuf:"bytes,1,rep,name=roleSelectors"`
Rules []rbacv1.PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -212,7 +213,7 @@ type GlobalRoleBinding struct {
// +optional
Subjects []rbacv1.Subject `json:"subjects,omitempty" protobuf:"bytes,2,rep,name=subjects"`
// RoleRef can only reference a ClusterRole in the global namespace.
// RoleRef can only reference a GlobalRole.
// If the RoleRef cannot be resolved, the Authorizer must return an error.
RoleRef rbacv1.RoleRef `json:"roleRef" protobuf:"bytes,3,opt,name=roleRef"`
}
@@ -233,7 +234,7 @@ type GlobalRoleBindingList struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:printcolumn:name="Workspace",type="string",JSONPath=".metadata.labels.kubesphere\\.io/workspace"
// +kubebuilder:printcolumn:name="Alias",type="string",JSONPath=".metadata.labels.kubesphere\\.io/alias-name"
// +kubebuilder:printcolumn:name="Alias",type="string",JSONPath=".metadata.annotations.kubesphere\\.io/alias-name"
// +kubebuilder:resource:categories="iam",scope="Cluster"
type WorkspaceRole struct {
metav1.TypeMeta `json:",inline"`
@@ -241,12 +242,9 @@ type WorkspaceRole struct {
// +optional
metav1.ObjectMeta `json:"metadata,omitempty"`
// Rules holds all the PolicyRules for this ClusterRole
// Rules holds all the PolicyRules for this WorkspaceRole
// +optional
Rules []rbacv1.PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"`
// AggregationRule is an optional field that describes how to build the Rules for this WorkspaceRole.
// If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be
// stomped by the controller.
AggregationRule *AggregationRule `json:"aggregationRule,omitempty" protobuf:"bytes,3,opt,name=aggregationRule"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -273,7 +271,7 @@ type WorkspaceRoleBinding struct {
// +optional
Subjects []rbacv1.Subject `json:"subjects,omitempty" protobuf:"bytes,2,rep,name=subjects"`
// RoleRef can only reference a ClusterRole in the global namespace.
// RoleRef can only reference a WorkspaceRole.
// If the RoleRef cannot be resolved, the Authorizer must return an error.
RoleRef rbacv1.RoleRef `json:"roleRef" protobuf:"bytes,3,opt,name=roleRef"`
}
@@ -286,8 +284,3 @@ type WorkspaceRoleBindingList struct {
metav1.ListMeta `json:"metadata,omitempty"`
Items []WorkspaceRoleBinding `json:"items"`
}
type UserDetail struct {
*User
GlobalRole *GlobalRole `json:"globalRole"`
}

View File

@@ -22,32 +22,9 @@ package v1alpha2
import (
"k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AggregationRule) DeepCopyInto(out *AggregationRule) {
*out = *in
if in.RoleSelectors != nil {
in, out := &in.RoleSelectors, &out.RoleSelectors
*out = make([]metav1.LabelSelector, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AggregationRule.
func (in *AggregationRule) DeepCopy() *AggregationRule {
if in == nil {
return nil
}
out := new(AggregationRule)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalRole) DeepCopyInto(out *GlobalRole) {
*out = *in
@@ -60,11 +37,6 @@ func (in *GlobalRole) DeepCopyInto(out *GlobalRole) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AggregationRule != nil {
in, out := &in.AggregationRule, &out.AggregationRule
*out = new(AggregationRule)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalRole.
@@ -223,31 +195,6 @@ func (in *UserCondition) DeepCopy() *UserCondition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UserDetail) DeepCopyInto(out *UserDetail) {
*out = *in
if in.User != nil {
in, out := &in.User, &out.User
*out = new(User)
(*in).DeepCopyInto(*out)
}
if in.GlobalRole != nil {
in, out := &in.GlobalRole, &out.GlobalRole
*out = new(GlobalRole)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserDetail.
func (in *UserDetail) DeepCopy() *UserDetail {
if in == nil {
return nil
}
out := new(UserDetail)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UserList) DeepCopyInto(out *UserList) {
*out = *in
@@ -288,11 +235,6 @@ func (in *UserSpec) DeepCopyInto(out *UserSpec) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Finalizers != nil {
in, out := &in.Finalizers, &out.Finalizers
*out = make([]FinalizerName, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec.
@@ -339,11 +281,6 @@ func (in *WorkspaceRole) DeepCopyInto(out *WorkspaceRole) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AggregationRule != nil {
in, out := &in.AggregationRule, &out.AggregationRule
*out = new(AggregationRule)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceRole.

View File

@@ -21,9 +21,12 @@ import (
k8sruntime "k8s.io/apimachinery/pkg/runtime"
urlruntime "k8s.io/apimachinery/pkg/util/runtime"
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
tenantv1alpha2 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha2"
)
func Install(scheme *k8sruntime.Scheme) {
urlruntime.Must(tenantv1alpha1.AddToScheme(scheme))
urlruntime.Must(tenantv1alpha2.AddToScheme(scheme))
urlruntime.Must(scheme.SetVersionPriority(tenantv1alpha1.SchemeGroupVersion))
urlruntime.Must(scheme.SetVersionPriority(tenantv1alpha2.SchemeGroupVersion))
}

View File

@@ -50,6 +50,7 @@ type WorkspaceStatus struct {
// Workspace is the Schema for the workspaces API
// +k8s:openapi-gen=true
// +kubebuilder:resource:categories="tenant",scope="Cluster"
type Workspace struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

View File

@@ -0,0 +1,23 @@
/*
Copyright 2019 The KubeSphere 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 v1alpha2 contains API Schema definitions for the tenant v1alpha2 API group
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package,register
// +k8s:conversion-gen=kubesphere.io/kubesphere/pkg/apis/tenant
// +k8s:defaulter-gen=TypeMeta
// +groupName=tenant.kubesphere.io
package v1alpha2

View File

@@ -0,0 +1,46 @@
/*
Copyright 2019 The KubeSphere 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.
*/
// NOTE: Boilerplate only. Ignore this file.
// Package v1alpha2 contains API Schema definitions for the tenant v1alpha2 API group
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package,register
// +k8s:conversion-gen=kubesphere.io/kubesphere/pkg/apis/tenant
// +k8s:defaulter-gen=TypeMeta
// +groupName=tenant.kubesphere.io
package v1alpha2
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/runtime/scheme"
)
var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: "tenant.kubesphere.io", Version: "v1alpha2"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
// AddToScheme is required by pkg/client/...
AddToScheme = SchemeBuilder.AddToScheme
)
// Resource is required by pkg/client/listers/...
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}

View File

@@ -0,0 +1,55 @@
/*
Copyright 2019 The KubeSphere 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 v1alpha2
import (
"log"
"os"
"path/filepath"
"testing"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
)
var cfg *rest.Config
var c client.Client
func TestMain(m *testing.M) {
t := &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crds")},
}
err := SchemeBuilder.AddToScheme(scheme.Scheme)
if err != nil {
log.Fatal(err)
}
if cfg, err = t.Start(); err != nil {
log.Fatal(err)
}
if c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}); err != nil {
log.Fatal(err)
}
code := m.Run()
t.Stop()
os.Exit(code)
}

View File

@@ -0,0 +1,62 @@
/*
Copyright 2019 The KubeSphere 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 v1alpha2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
)
const (
ResourceKindWorkspaceTemplate = "WorkspaceTemplate"
ResourceSingularWorkspaceTemplate = "workspacetemplate"
ResourcePluralWorkspaceTemplate = "workspacetemplates"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +genclient:nonNamespaced
// WorkspaceTemplate is the Schema for the workspacetemplates API
// +k8s:openapi-gen=true
// +kubebuilder:resource:categories="tenant",scope="Cluster"
type WorkspaceTemplate struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec WorkspaceTemplateSpec `json:"spec,omitempty"`
}
type WorkspaceTemplateSpec struct {
v1alpha1.WorkspaceSpec `json:",inline"`
// authorized clusters
// +optional
Clusters []string `json:"clusters,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +genclient:nonNamespaced
// WorkspaceTemplateList contains a list of WorkspaceTemplate
type WorkspaceTemplateList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []WorkspaceTemplate `json:"items"`
}
func init() {
SchemeBuilder.Register(&WorkspaceTemplate{}, &WorkspaceTemplateList{})
}

View File

@@ -0,0 +1,57 @@
/*
Copyright 2019 The KubeSphere 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 v1alpha2
import (
"testing"
"github.com/onsi/gomega"
"golang.org/x/net/context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
func TestStorageWorkspaceTemplate(t *testing.T) {
key := types.NamespacedName{
Name: "foo",
}
created := &WorkspaceTemplate{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
}
g := gomega.NewGomegaWithT(t)
// Test Create
fetched := &WorkspaceTemplate{}
g.Expect(c.Create(context.TODO(), created)).To(gomega.Succeed())
g.Expect(c.Get(context.TODO(), key, fetched)).To(gomega.Succeed())
g.Expect(fetched).To(gomega.Equal(created))
// Test Updating the Labels
updated := fetched.DeepCopy()
updated.Labels = map[string]string{"hello": "world"}
g.Expect(c.Update(context.TODO(), updated)).To(gomega.Succeed())
g.Expect(c.Get(context.TODO(), key, fetched)).To(gomega.Succeed())
g.Expect(fetched).To(gomega.Equal(updated))
// Test Delete
g.Expect(c.Delete(context.TODO(), fetched)).To(gomega.Succeed())
g.Expect(c.Get(context.TODO(), key, fetched)).ToNot(gomega.Succeed())
}

View File

@@ -0,0 +1,104 @@
// +build !ignore_autogenerated
/*
Copyright 2019 The KubeSphere 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.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha2
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkspaceTemplate) DeepCopyInto(out *WorkspaceTemplate) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceTemplate.
func (in *WorkspaceTemplate) DeepCopy() *WorkspaceTemplate {
if in == nil {
return nil
}
out := new(WorkspaceTemplate)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *WorkspaceTemplate) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkspaceTemplateList) DeepCopyInto(out *WorkspaceTemplateList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]WorkspaceTemplate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceTemplateList.
func (in *WorkspaceTemplateList) DeepCopy() *WorkspaceTemplateList {
if in == nil {
return nil
}
out := new(WorkspaceTemplateList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *WorkspaceTemplateList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkspaceTemplateSpec) DeepCopyInto(out *WorkspaceTemplateSpec) {
*out = *in
out.WorkspaceSpec = in.WorkspaceSpec
if in.Clusters != nil {
in, out := &in.Clusters, &out.Clusters
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceTemplateSpec.
func (in *WorkspaceTemplateSpec) DeepCopy() *WorkspaceTemplateSpec {
if in == nil {
return nil
}
out := new(WorkspaceTemplateSpec)
in.DeepCopyInto(out)
return out
}