diff --git a/config/ks-core/charts/ks-crds/crds/application.kubesphere.io_applicationreleases.yaml b/config/ks-core/charts/ks-crds/crds/application.kubesphere.io_applicationreleases.yaml index a13550b49..bf39d8190 100644 --- a/config/ks-core/charts/ks-crds/crds/application.kubesphere.io_applicationreleases.yaml +++ b/config/ks-core/charts/ks-crds/crds/application.kubesphere.io_applicationreleases.yaml @@ -96,7 +96,7 @@ spec: items: description: |- RawMessage is a raw encoded JSON value. - It implements Marshaler and Unmarshaler and can + It implements [Marshaler] and [Unmarshaler] and can be used to delay JSON decoding or precompute a JSON encoding. format: byte type: string diff --git a/config/ks-core/charts/ks-crds/crds/application.kubesphere.io_repos.yaml b/config/ks-core/charts/ks-crds/crds/application.kubesphere.io_repos.yaml index fd6054a47..cab3a3d57 100644 --- a/config/ks-core/charts/ks-crds/crds/application.kubesphere.io_repos.yaml +++ b/config/ks-core/charts/ks-crds/crds/application.kubesphere.io_repos.yaml @@ -85,6 +85,7 @@ spec: url: type: string required: + - syncPeriod - url type: object status: diff --git a/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_apiservices.yaml b/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_apiservices.yaml index e3f933ec9..db4f405a6 100644 --- a/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_apiservices.yaml +++ b/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_apiservices.yaml @@ -92,16 +92,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -142,12 +134,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_extensionentries.yaml b/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_extensionentries.yaml index 911c2c0f7..1b7a7ed1a 100644 --- a/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_extensionentries.yaml +++ b/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_extensionentries.yaml @@ -49,16 +49,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -99,12 +91,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_jsbundles.yaml b/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_jsbundles.yaml index d855274cb..c446a293b 100644 --- a/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_jsbundles.yaml +++ b/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_jsbundles.yaml @@ -55,10 +55,13 @@ spec: description: The key to select. type: string name: + default: "" description: |- Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string namespace: type: string @@ -81,10 +84,13 @@ spec: a valid secret key. type: string name: + default: "" description: |- Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string namespace: type: string @@ -140,16 +146,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -190,12 +188,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_reverseproxies.yaml b/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_reverseproxies.yaml index 1c01aae66..93ce29129 100644 --- a/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_reverseproxies.yaml +++ b/config/ks-core/charts/ks-crds/crds/extensions.kubesphere.io_reverseproxies.yaml @@ -149,16 +149,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -199,12 +191,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_builtinroles.yaml b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_builtinroles.yaml index de3c37335..7ad0009e4 100644 --- a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_builtinroles.yaml +++ b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_builtinroles.yaml @@ -73,11 +73,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string diff --git a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_clusterroles.yaml b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_clusterroles.yaml index 490c64558..d564d42ba 100644 --- a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_clusterroles.yaml +++ b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_clusterroles.yaml @@ -55,11 +55,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -109,6 +111,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic nonResourceURLs: description: |- 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 @@ -117,24 +120,28 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic resourceNames: description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. items: type: string type: array + x-kubernetes-list-type: atomic resources: description: Resources is a list of resources this rule applies to. '*' represents all resources. items: type: string type: array + x-kubernetes-list-type: atomic verbs: description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. items: type: string type: array + x-kubernetes-list-type: atomic required: - verbs type: object diff --git a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_globalroles.yaml b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_globalroles.yaml index e63aebbd7..4408e7aa0 100644 --- a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_globalroles.yaml +++ b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_globalroles.yaml @@ -52,6 +52,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic nonResourceURLs: description: |- 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 @@ -60,24 +61,28 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic resourceNames: description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. items: type: string type: array + x-kubernetes-list-type: atomic resources: description: Resources is a list of resources this rule applies to. '*' represents all resources. items: type: string type: array + x-kubernetes-list-type: atomic verbs: description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. items: type: string type: array + x-kubernetes-list-type: atomic required: - verbs type: object @@ -124,11 +129,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -178,6 +185,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic nonResourceURLs: description: |- 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 @@ -186,24 +194,28 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic resourceNames: description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. items: type: string type: array + x-kubernetes-list-type: atomic resources: description: Resources is a list of resources this rule applies to. '*' represents all resources. items: type: string type: array + x-kubernetes-list-type: atomic verbs: description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. items: type: string type: array + x-kubernetes-list-type: atomic required: - verbs type: object diff --git a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_roles.yaml b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_roles.yaml index 53828ec33..1543728a3 100644 --- a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_roles.yaml +++ b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_roles.yaml @@ -55,11 +55,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -109,6 +111,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic nonResourceURLs: description: |- 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 @@ -117,24 +120,28 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic resourceNames: description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. items: type: string type: array + x-kubernetes-list-type: atomic resources: description: Resources is a list of resources this rule applies to. '*' represents all resources. items: type: string type: array + x-kubernetes-list-type: atomic verbs: description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. items: type: string type: array + x-kubernetes-list-type: atomic required: - verbs type: object diff --git a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_roletemplates.yaml b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_roletemplates.yaml index 3b8bd6bf3..11e51ab29 100644 --- a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_roletemplates.yaml +++ b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_roletemplates.yaml @@ -64,6 +64,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic nonResourceURLs: description: |- 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 @@ -72,6 +73,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic resourceNames: description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything @@ -79,18 +81,21 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic resources: description: Resources is a list of resources this rule applies to. '*' represents all resources. items: type: string type: array + x-kubernetes-list-type: atomic verbs: description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. items: type: string type: array + x-kubernetes-list-type: atomic required: - verbs type: object diff --git a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_workspaceroles.yaml b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_workspaceroles.yaml index 1ab93a652..c511fae51 100644 --- a/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_workspaceroles.yaml +++ b/config/ks-core/charts/ks-crds/crds/iam.kubesphere.io_workspaceroles.yaml @@ -59,6 +59,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic nonResourceURLs: description: |- 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 @@ -67,24 +68,28 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic resourceNames: description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. items: type: string type: array + x-kubernetes-list-type: atomic resources: description: Resources is a list of resources this rule applies to. '*' represents all resources. items: type: string type: array + x-kubernetes-list-type: atomic verbs: description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. items: type: string type: array + x-kubernetes-list-type: atomic required: - verbs type: object @@ -139,11 +144,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -193,6 +200,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic nonResourceURLs: description: |- 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 @@ -201,24 +209,28 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic resourceNames: description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. items: type: string type: array + x-kubernetes-list-type: atomic resources: description: Resources is a list of resources this rule applies to. '*' represents all resources. items: type: string type: array + x-kubernetes-list-type: atomic verbs: description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. items: type: string type: array + x-kubernetes-list-type: atomic required: - verbs type: object diff --git a/config/ks-core/charts/ks-crds/crds/kubesphere.io_extensions.yaml b/config/ks-core/charts/ks-crds/crds/kubesphere.io_extensions.yaml index 715bc687d..f130e4447 100644 --- a/config/ks-core/charts/ks-crds/crds/kubesphere.io_extensions.yaml +++ b/config/ks-core/charts/ks-crds/crds/kubesphere.io_extensions.yaml @@ -86,18 +86,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of - the current state of this API Resource.\n---\nThis struct - is intended for direct use as an array at the field path - .status.conditions. For example,\n\n\n\ttype FooStatus - struct{\n\t // Represents the observations of a foo's - current state.\n\t // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // - +listType=map\n\t // +listMapKey=type\n\t Conditions - []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" - patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of + the current state of this API Resource. properties: lastTransitionTime: description: |- @@ -139,12 +129,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -187,16 +172,8 @@ spec: type: object conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -237,12 +214,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/ks-core/charts/ks-crds/crds/kubesphere.io_extensionversions.yaml b/config/ks-core/charts/ks-crds/crds/kubesphere.io_extensionversions.yaml index 0ca41e9f4..f64c6c593 100644 --- a/config/ks-core/charts/ks-crds/crds/kubesphere.io_extensionversions.yaml +++ b/config/ks-core/charts/ks-crds/crds/kubesphere.io_extensionversions.yaml @@ -51,10 +51,13 @@ spec: description: The key to select. type: string name: + default: "" description: |- Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid? type: string namespace: type: string diff --git a/config/ks-core/charts/ks-crds/crds/kubesphere.io_installplans.yaml b/config/ks-core/charts/ks-crds/crds/kubesphere.io_installplans.yaml index af0eb32f7..967afb209 100644 --- a/config/ks-core/charts/ks-crds/crds/kubesphere.io_installplans.yaml +++ b/config/ks-core/charts/ks-crds/crds/kubesphere.io_installplans.yaml @@ -84,11 +84,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -134,18 +136,8 @@ spec: properties: conditions: items: - description: "Condition contains details for one aspect of - the current state of this API Resource.\n---\nThis struct - is intended for direct use as an array at the field path - .status.conditions. For example,\n\n\n\ttype FooStatus - struct{\n\t // Represents the observations of a foo's - current state.\n\t // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // - +listType=map\n\t // +listMapKey=type\n\t Conditions - []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" - patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of + the current state of this API Resource. properties: lastTransitionTime: description: |- @@ -187,12 +179,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -235,16 +222,8 @@ spec: type: object conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -285,12 +264,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/ks-core/charts/ks-crds/crds/kubesphere.io_serviceaccounts.yaml b/config/ks-core/charts/ks-crds/crds/kubesphere.io_serviceaccounts.yaml index 6ee1b1191..38b8a2df9 100644 --- a/config/ks-core/charts/ks-crds/crds/kubesphere.io_serviceaccounts.yaml +++ b/config/ks-core/charts/ks-crds/crds/kubesphere.io_serviceaccounts.yaml @@ -37,24 +37,8 @@ spec: type: object secrets: items: - description: |- - ObjectReference contains enough information to let you inspect or modify the referred object. - --- - New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. - 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. - 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular - restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". - Those cannot be well described when embedded. - 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. - 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity - during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple - and the version of the actual struct is irrelevant. - 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type - will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. - - - Instead of using this type, create a locally provided and used type that is well-focused on your reference. - For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 . + description: ObjectReference contains enough information to let you + inspect or modify the referred object. properties: apiVersion: description: API version of the referent. @@ -68,7 +52,6 @@ spec: the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. - TODO: this design is not final and this field is subject to change in the future. type: string kind: description: |- diff --git a/config/ks-core/charts/ks-crds/crds/quota.kubesphere.io_resourcequotas.yaml b/config/ks-core/charts/ks-crds/crds/quota.kubesphere.io_resourcequotas.yaml index f449387c6..1fdc986da 100644 --- a/config/ks-core/charts/ks-crds/crds/quota.kubesphere.io_resourcequotas.yaml +++ b/config/ks-core/charts/ks-crds/crds/quota.kubesphere.io_resourcequotas.yaml @@ -88,11 +88,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - operator - scopeName type: object type: array + x-kubernetes-list-type: atomic type: object x-kubernetes-map-type: atomic scopes: @@ -104,6 +106,7 @@ spec: match each object tracked by a quota type: string type: array + x-kubernetes-list-type: atomic type: object selector: additionalProperties: diff --git a/config/ks-core/charts/ks-crds/crds/tenant.kubesphere.io_workspacetemplates.yaml b/config/ks-core/charts/ks-crds/crds/tenant.kubesphere.io_workspacetemplates.yaml index 05b8af36d..0efdf458f 100644 --- a/config/ks-core/charts/ks-crds/crds/tenant.kubesphere.io_workspacetemplates.yaml +++ b/config/ks-core/charts/ks-crds/crds/tenant.kubesphere.io_workspacetemplates.yaml @@ -97,11 +97,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -201,11 +203,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string diff --git a/config/ks-core/templates/roletemplates.yaml b/config/ks-core/templates/roletemplates.yaml index 8f60fdae9..9c7a6064d 100644 --- a/config/ks-core/templates/roletemplates.yaml +++ b/config/ks-core/templates/roletemplates.yaml @@ -1569,6 +1569,63 @@ spec: --- apiVersion: iam.kubesphere.io/v1beta1 kind: RoleTemplate +metadata: + annotations: + iam.kubesphere.io/role-template-rules: '{"workloadtemplates": "view"}' + labels: + iam.kubesphere.io/aggregate-to-operator: "" + iam.kubesphere.io/aggregate-to-viewer: "" + iam.kubesphere.io/category: namespace-configuration-management + iam.kubesphere.io/scope: "namespace" + kubesphere.io/managed: "true" + name: namespace-view-workloadtemplates +spec: + description: + en: 'View workloadtemplates in the project.' + zh: '查看项目中的工作负载模板。' + displayName: + en: workloadtemplate Viewing + zh: '工作负载模板查看' + rules: + - apiGroups: + - 'workloadtemplate.kubesphere.io' + resources: + - "*" + verbs: + - get + - list + - watch + +--- +apiVersion: iam.kubesphere.io/v1beta1 +kind: RoleTemplate +metadata: + annotations: + iam.kubesphere.io/dependencies: '["namespace-view-workloadtemplates"]' + iam.kubesphere.io/role-template-rules: '{"workloadtemplates": "manage"}' + labels: + iam.kubesphere.io/aggregate-to-operator: "" + iam.kubesphere.io/category: namespace-configuration-management + iam.kubesphere.io/scope: "namespace" + kubesphere.io/managed: "true" + name: namespace-manage-workloadtemplates +spec: + description: + en: 'Create, edit, and delete workloadtemplates in the project.' + zh: '创建、编辑和删除项目中的工作负载模板。' + displayName: + en: workloadtemplate Management + zh: '工作负载模板管理' + rules: + - apiGroups: + - 'workloadtemplate.kubesphere.io' + resources: + - "*" + verbs: + - '*' +--- +apiVersion: iam.kubesphere.io/v1beta1 +kind: RoleTemplate metadata: annotations: iam.kubesphere.io/role-template-rules: '{"secrets": "view"}' @@ -1794,6 +1851,7 @@ metadata: kubesphere.io/managed: "true" iam.kubesphere.io/aggregate-to-self-provisioner: "" iam.kubesphere.io/aggregate-to-viewer: "" + iam.kubesphere.io/aggregate-to-regular: "" name: workspace-view-app-repos spec: description: diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 15b3a22f1..cffa0f73c 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -64,6 +64,7 @@ import ( tenantapiv1beta1 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1beta1" terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2" "kubesphere.io/kubesphere/pkg/kapis/version" + workloadtemplatev1alpha1 "kubesphere.io/kubesphere/pkg/kapis/workloadtemplate/v1alpha1" "kubesphere.io/kubesphere/pkg/models/auth" "kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/models/iam/im" @@ -186,6 +187,7 @@ func (s *APIServer) installKubeSphereAPIs() { packagev1alpha1.NewHandler(s.RuntimeCache), gatewayv1alpha2.NewHandler(s.RuntimeCache), appv2.NewHandler(s.RuntimeClient, s.ClusterClient, s.S3Options), + workloadtemplatev1alpha1.NewHandler(s.RuntimeClient, s.K8sVersion, rbacAuthorizer), static.NewHandler(s.CacheClient), } diff --git a/pkg/controller/application/helm_repo_controller.go b/pkg/controller/application/helm_repo_controller.go index 0970bd9a5..9261a8d15 100644 --- a/pkg/controller/application/helm_repo_controller.go +++ b/pkg/controller/application/helm_repo_controller.go @@ -24,6 +24,8 @@ import ( "strings" "time" + "k8s.io/utils/ptr" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "kubesphere.io/api/constants" @@ -119,25 +121,22 @@ func (r *RepoReconciler) UpdateStatus(ctx context.Context, helmRepo *appv2.Repo) klog.Errorf("update status failed, error: %s", err) return err } - klog.Infof("update status successfully, repo: %s", helmRepo.GetName()) + klog.Infof("update repo %s status: %s", helmRepo.GetName(), helmRepo.Status.State) return nil } -func (r *RepoReconciler) noNeedSync(ctx context.Context, helmRepo *appv2.Repo) (bool, error) { - if helmRepo.Spec.SyncPeriod == 0 { - if helmRepo.Status.State != appv2.StatusNosync { - helmRepo.Status.State = appv2.StatusNosync - klog.Infof("no sync when SyncPeriod=0, repo: %s", helmRepo.GetName()) - if err := r.UpdateStatus(ctx, helmRepo); err != nil { - klog.Errorf("update status failed, error: %s", err) - return false, err - } - } - klog.Infof("no sync when SyncPeriod=0, repo: %s", helmRepo.GetName()) +func (r *RepoReconciler) skipSync(helmRepo *appv2.Repo) (bool, error) { + if helmRepo.Status.State == appv2.StatusManualTrigger || helmRepo.Status.State == appv2.StatusSyncing { + klog.Infof("repo: %s state: %s", helmRepo.GetName(), helmRepo.Status.State) + return false, nil + } + + if helmRepo.Spec.SyncPeriod == nil || *helmRepo.Spec.SyncPeriod == 0 { + klog.Infof("repo: %s no sync SyncPeriod=0", helmRepo.GetName()) return true, nil } passed := time.Since(helmRepo.Status.LastUpdateTime.Time).Seconds() - if helmRepo.Status.State == appv2.StatusSuccessful && passed < float64(helmRepo.Spec.SyncPeriod) { + if helmRepo.Status.State == appv2.StatusSuccessful && passed < float64(*helmRepo.Spec.SyncPeriod) { klog.Infof("last sync time is %s, passed %f, no need to sync, repo: %s", helmRepo.Status.LastUpdateTime, passed, helmRepo.GetName()) return true, nil } @@ -172,6 +171,17 @@ func (r *RepoReconciler) Reconcile(ctx context.Context, request reconcile.Reques klog.Errorf("get helm repo failed, error: %s", err) return reconcile.Result{}, client.IgnoreNotFound(err) } + if helmRepo.Status.State == "" { + helmRepo.Status.State = appv2.StatusCreated + err := r.UpdateStatus(ctx, helmRepo) + if err != nil { + return reconcile.Result{}, err + } + } + + if helmRepo.Spec.SyncPeriod == nil { + helmRepo.Spec.SyncPeriod = ptr.To(0) + } workspaceTemplate := &tenantv1beta1.WorkspaceTemplate{} workspaceName := helmRepo.Labels[constants.WorkspaceLabelKey] @@ -188,8 +198,8 @@ func (r *RepoReconciler) Reconcile(ctx context.Context, request reconcile.Reques } } - requeueAfter := time.Duration(helmRepo.Spec.SyncPeriod) * time.Second - noSync, err := r.noNeedSync(ctx, helmRepo) + requeueAfter := time.Duration(*helmRepo.Spec.SyncPeriod) * time.Second + noSync, err := r.skipSync(helmRepo) if err != nil { return reconcile.Result{}, err } @@ -245,7 +255,7 @@ func (r *RepoReconciler) Reconcile(ctx context.Context, request reconcile.Reques versions = filterVersions(versions) - vRequests, err := repoParseRequest(r.Client, versions, helmRepo, appName) + vRequests, err := repoParseRequest(r.Client, versions, helmRepo, appName, appList) if err != nil { klog.Errorf("parse request failed, error: %s", err) return reconcile.Result{}, err @@ -254,7 +264,7 @@ func (r *RepoReconciler) Reconcile(ctx context.Context, request reconcile.Reques continue } - klog.Infof("found %d/%d versions for %s need to upgrade", len(vRequests), len(versions), appName) + klog.Infof("found %d/%d versions for %s need to upgrade or create", len(vRequests), len(versions), appName) own := metav1.OwnerReference{ APIVersion: appv2.SchemeGroupVersion.String(), @@ -280,7 +290,7 @@ func (r *RepoReconciler) Reconcile(ctx context.Context, request reconcile.Reques return reconcile.Result{RequeueAfter: requeueAfter}, nil } -func repoParseRequest(cli client.Client, versions helmrepo.ChartVersions, helmRepo *appv2.Repo, appName string) (result []application.AppRequest, err error) { +func repoParseRequest(cli client.Client, versions helmrepo.ChartVersions, helmRepo *appv2.Repo, appName string, appList *appv2.ApplicationList) (createOrUpdateList []application.AppRequest, err error) { appVersionList := &appv2.ApplicationVersionList{} appID := fmt.Sprintf("%s-%s", helmRepo.Name, application.GenerateShortNameMD5Hash(appName)) @@ -320,11 +330,10 @@ func repoParseRequest(cli client.Client, versions helmrepo.ChartVersions, helmRe appVersionDigestMap[key] = i.Spec.Digest } } - + var legalVersion, shortName string for _, ver := range versions { - - legalVersion := application.FormatVersion(ver.Version) - shortName := application.GenerateShortNameMD5Hash(ver.Name) + legalVersion = application.FormatVersion(ver.Version) + shortName = application.GenerateShortNameMD5Hash(ver.Name) key := fmt.Sprintf("%s-%s-%s", helmRepo.Name, shortName, legalVersion) dig := appVersionDigestMap[key] if dig == ver.Digest { @@ -333,39 +342,59 @@ func repoParseRequest(cli client.Client, versions helmrepo.ChartVersions, helmRe if dig != "" { klog.Infof("digest not match, key: %s, digest: %s, ver.Digest: %s", key, dig, ver.Digest) } - - vRequest := application.AppRequest{ - RepoName: helmRepo.Name, - VersionName: ver.Version, - AppName: fmt.Sprintf("%s-%s", helmRepo.Name, shortName), - AliasName: appName, - OriginalName: appName, - AppHome: ver.Home, - Icon: ver.Icon, - Digest: ver.Digest, - Description: ver.Description, - Abstraction: ver.Description, - Maintainers: application.GetMaintainers(ver.Maintainers), - AppType: appv2.AppTypeHelm, - Workspace: helmRepo.GetWorkspace(), - Credential: helmRepo.Spec.Credential, - FromRepo: true, - } - url := ver.URLs[0] - methodList := []string{"https://", "http://", "s3://", "oci://"} - needContact := true - for _, method := range methodList { - if strings.HasPrefix(url, method) { - needContact = false - break - } - } - - if needContact { - url = strings.TrimSuffix(helmRepo.Spec.Url, "/") + "/" + url - } - vRequest.PullUrl = url - result = append(result, vRequest) + vRequest := generateVRequest(helmRepo, ver, shortName, appName) + createOrUpdateList = append(createOrUpdateList, vRequest) } - return result, nil + + appNotFound := true + for _, i := range appList.Items { + if i.Name == appID { + appNotFound = false + break + } + } + + if len(createOrUpdateList) == 0 && len(versions) > 0 && appNotFound { + //The repo source has been deleted, but the appversion has not been deleted due to the existence of the instance, + //and the appversion that is scheduled to be updated is empty + //so you need to ensure that at least one version is used to create the app + ver := versions[0] + v := generateVRequest(helmRepo, ver, shortName, appName) + createOrUpdateList = append(createOrUpdateList, v) + } + return createOrUpdateList, nil +} + +func generateVRequest(helmRepo *appv2.Repo, ver *helmrepo.ChartVersion, shortName string, appName string) application.AppRequest { + vRequest := application.AppRequest{ + RepoName: helmRepo.Name, + VersionName: ver.Version, + AppName: fmt.Sprintf("%s-%s", helmRepo.Name, shortName), + AliasName: appName, + OriginalName: appName, + AppHome: ver.Home, + Icon: ver.Icon, + Digest: ver.Digest, + Description: ver.Description, + Abstraction: ver.Description, + Maintainers: application.GetMaintainers(ver.Maintainers), + AppType: appv2.AppTypeHelm, + Workspace: helmRepo.GetWorkspace(), + Credential: helmRepo.Spec.Credential, + FromRepo: true, + } + url := ver.URLs[0] + methodList := []string{"https://", "http://", "s3://", "oci://"} + needContact := true + for _, method := range methodList { + if strings.HasPrefix(url, method) { + needContact = false + break + } + } + if needContact { + url = strings.TrimSuffix(helmRepo.Spec.Url, "/") + "/" + url + } + vRequest.PullUrl = url + return vRequest } diff --git a/pkg/kapis/application/v2/errors.go b/pkg/kapis/application/v2/errors.go index bb23e1afd..72dbc103c 100644 --- a/pkg/kapis/application/v2/errors.go +++ b/pkg/kapis/application/v2/errors.go @@ -9,19 +9,13 @@ import ( "fmt" goruntime "runtime" - "kubesphere.io/kubesphere/pkg/server/params" - "github.com/emicklei/go-restful/v3" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" "kubesphere.io/kubesphere/pkg/api" - "kubesphere.io/kubesphere/pkg/apiserver/query" - resv1beta1 "kubesphere.io/kubesphere/pkg/models/resources/v1beta1" ) func (h *appHandler) conflictedDone(req *restful.Request, resp *restful.Response, pathParam string, obj client.Object) bool { @@ -74,32 +68,3 @@ func requestDone(err error, resp *restful.Response) bool { } return false } -func removeQueryArg(req *restful.Request, args ...string) { - //The default filter is a whitelist, so delete some of our custom logical parameters - for _, i := range args { - q := req.Request.URL.Query() - q.Del(i) - req.Request.URL.RawQuery = q.Encode() - } -} - -func convertToListResult(obj runtime.Object, req *restful.Request) (listResult api.ListResult) { - removeQueryArg(req, params.ConditionsParam, "global", "create") - _ = meta.EachListItem(obj, omitManagedFields) - queryParams := query.ParseQueryParameter(req) - list, _ := meta.ExtractList(obj) - items, _, totalCount := resv1beta1.DefaultList(list, queryParams, resv1beta1.DefaultCompare, resv1beta1.DefaultFilter) - - listResult.Items = items - listResult.TotalItems = totalCount - - return listResult -} -func omitManagedFields(o runtime.Object) error { - a, err := meta.Accessor(o) - if err != nil { - return err - } - a.SetManagedFields(nil) - return nil -} diff --git a/pkg/kapis/application/v2/handler_app.go b/pkg/kapis/application/v2/handler_app.go index cc1a0e96f..64a4610cc 100644 --- a/pkg/kapis/application/v2/handler_app.go +++ b/pkg/kapis/application/v2/handler_app.go @@ -15,9 +15,9 @@ import ( "strings" "time" - "kubesphere.io/kubesphere/pkg/utils/stringutils" - + k8suitl "kubesphere.io/kubesphere/pkg/utils/k8sutil" "kubesphere.io/kubesphere/pkg/utils/sliceutil" + "kubesphere.io/kubesphere/pkg/utils/stringutils" "github.com/emicklei/go-restful/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,11 +48,12 @@ func (h *appHandler) CreateOrUpdateApp(req *restful.Request, resp *restful.Respo api.HandleBadRequest(resp, nil, fmt.Errorf("System has no OSS store, the maximum file size is %d", maxFileSize)) return } - - newReq, err := parseRequest(createAppRequest) + validate, _ := strconv.ParseBool(req.QueryParameter("validate")) + newReq, err := parseRequest(createAppRequest, validate) if requestDone(err, resp) { return } + data := map[string]any{ "icon": newReq.Icon, "appName": newReq.AppName, @@ -63,7 +64,6 @@ func (h *appHandler) CreateOrUpdateApp(req *restful.Request, resp *restful.Respo "resources": newReq.Resources, } - validate, _ := strconv.ParseBool(req.QueryParameter("validate")) if validate { resp.WriteAsJson(data) return @@ -163,7 +163,7 @@ func (h *appHandler) ListApps(req *restful.Request, resp *restful.Response) { filtered.Items = append(filtered.Items, curApp) } - resp.WriteEntity(convertToListResult(&filtered, req)) + resp.WriteEntity(k8suitl.ConvertToListResult(&filtered, req)) } func (h *appHandler) DescribeApp(req *restful.Request, resp *restful.Response) { diff --git a/pkg/kapis/application/v2/handler_apprls.go b/pkg/kapis/application/v2/handler_apprls.go index c3aaece15..75e4c2ef3 100644 --- a/pkg/kapis/application/v2/handler_apprls.go +++ b/pkg/kapis/application/v2/handler_apprls.go @@ -9,6 +9,8 @@ import ( "bytes" "encoding/json" + k8suitl "kubesphere.io/kubesphere/pkg/utils/k8sutil" + "kubesphere.io/kubesphere/pkg/apiserver/request" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -55,7 +57,7 @@ func (h *appHandler) CreateOrUpdateAppRls(req *restful.Request, resp *restful.Re } if createRlsRequest.Spec.AppType != appv2.AppTypeHelm { - runtimeClient, _, _, err := h.getCluster(createRlsRequest.GetRlsCluster()) + runtimeClient, _, _, err := h.getCluster(req, createRlsRequest.GetRlsCluster()) if requestDone(err, resp) { return } @@ -111,7 +113,7 @@ func (h *appHandler) DescribeAppRls(req *restful.Request, resp *restful.Response } app.SetManagedFields(nil) if app.Spec.AppType == appv2.AppTypeYaml || app.Spec.AppType == appv2.AppTypeEdge { - data, err := h.getRealTimeYaml(ctx, app) + data, err := h.getRealTimeYaml(ctx, req, app) if err != nil { klog.Errorf("getRealTimeYaml: %s", err.Error()) app.Status.RealTimeResources = nil @@ -123,7 +125,7 @@ func (h *appHandler) DescribeAppRls(req *restful.Request, resp *restful.Response return } - data, err := h.getRealTimeHelm(ctx, app) + data, err := h.getRealTimeHelm(ctx, req, app) if err != nil { klog.Errorf("getRealTimeHelm: %s", err.Error()) app.Status.RealTimeResources = nil @@ -135,8 +137,8 @@ func (h *appHandler) DescribeAppRls(req *restful.Request, resp *restful.Response resp.WriteEntity(app) } -func (h *appHandler) getRealTimeYaml(ctx context.Context, app *appv2.ApplicationRelease) (data []json.RawMessage, err error) { - runtimeClient, dynamicClient, cluster, err := h.getCluster(app.GetRlsCluster()) +func (h *appHandler) getRealTimeYaml(ctx context.Context, req *restful.Request, app *appv2.ApplicationRelease) (data []json.RawMessage, err error) { + runtimeClient, dynamicClient, cluster, err := h.getCluster(req, app.GetRlsCluster()) if err != nil { klog.Errorf("cluster: %s url: %s: %s", cluster.Name, cluster.Spec.Connection.KubernetesAPIEndpoint, err) return nil, err @@ -175,8 +177,8 @@ func (h *appHandler) getRealTimeYaml(ctx context.Context, app *appv2.Application return data, err } -func (h *appHandler) getRealTimeHelm(ctx context.Context, app *appv2.ApplicationRelease) (data []json.RawMessage, err error) { - runtimeClient, dynamicClient, cluster, err := h.getCluster(app.GetRlsCluster()) +func (h *appHandler) getRealTimeHelm(ctx context.Context, req *restful.Request, app *appv2.ApplicationRelease) (data []json.RawMessage, err error) { + runtimeClient, dynamicClient, cluster, err := h.getCluster(req, app.GetRlsCluster()) if err != nil { klog.Errorf("cluster: %s url: %s: %s", cluster.Name, cluster.Spec.Connection.KubernetesAPIEndpoint, err) return nil, err @@ -292,5 +294,5 @@ func (h *appHandler) ListAppRls(req *restful.Request, resp *restful.Response) { return } - resp.WriteEntity(convertToListResult(&appList, req)) + resp.WriteEntity(k8suitl.ConvertToListResult(&appList, req)) } diff --git a/pkg/kapis/application/v2/handler_appversion.go b/pkg/kapis/application/v2/handler_appversion.go index 753e80d3c..9ea42dc04 100644 --- a/pkg/kapis/application/v2/handler_appversion.go +++ b/pkg/kapis/application/v2/handler_appversion.go @@ -1,8 +1,3 @@ -/* - * Please refer to the LICENSE file in the root directory of the project. - * https://github.com/kubesphere/kubesphere/blob/master/LICENSE - */ - package v2 import ( @@ -11,12 +6,17 @@ import ( "strconv" "strings" + k8suitl "kubesphere.io/kubesphere/pkg/utils/k8sutil" + + "k8s.io/apimachinery/pkg/selection" + + "kubesphere.io/kubesphere/pkg/utils/stringutils" + "github.com/emicklei/go-restful/v3" "golang.org/x/net/context" "helm.sh/helm/v3/pkg/chart/loader" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" "k8s.io/klog/v2" appv2 "kubesphere.io/api/application/v2" runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" @@ -27,7 +27,6 @@ import ( "kubesphere.io/kubesphere/pkg/server/params" "kubesphere.io/kubesphere/pkg/simple/client/application" "kubesphere.io/kubesphere/pkg/utils/sliceutil" - "kubesphere.io/kubesphere/pkg/utils/stringutils" ) func (h *appHandler) CreateOrUpdateAppVersion(req *restful.Request, resp *restful.Response) { @@ -42,7 +41,13 @@ func (h *appHandler) CreateOrUpdateAppVersion(req *restful.Request, resp *restfu createAppVersionRequest.AppName = req.PathParameter("app") - vRequest, err := parseRequest(createAppVersionRequest) + workspace := req.PathParameter("workspace") + if workspace == "" { + workspace = appv2.SystemWorkspace + } + createAppVersionRequest.Workspace = workspace + validate, _ := strconv.ParseBool(req.QueryParameter("validate")) + vRequest, err := parseRequest(createAppVersionRequest, validate) if requestDone(err, resp) { return } @@ -55,7 +60,6 @@ func (h *appHandler) CreateOrUpdateAppVersion(req *restful.Request, resp *restfu "aliasName": vRequest.AliasName, } - validate, _ := strconv.ParseBool(req.QueryParameter("validate")) if validate { resp.WriteAsJson(data) return @@ -154,7 +158,7 @@ func (h *appHandler) ListAppVersions(req *restful.Request, resp *restful.Respons filtered.Items = append(filtered.Items, appv) } - resp.WriteEntity(convertToListResult(&filtered, req)) + resp.WriteEntity(k8suitl.ConvertToListResult(&filtered, req)) } func (h *appHandler) GetAppVersionPackage(req *restful.Request, resp *restful.Response) { @@ -322,7 +326,7 @@ func (h *appHandler) ListReviews(req *restful.Request, resp *restful.Response) { } if conditions == nil || len(conditions.Match) == 0 { - resp.WriteEntity(convertToListResult(&appVersions, req)) + resp.WriteEntity(k8suitl.ConvertToListResult(&appVersions, req)) return } @@ -335,5 +339,5 @@ func (h *appHandler) ListReviews(req *restful.Request, resp *restful.Response) { filteredAppVersions.Items = append(filteredAppVersions.Items, version) } - resp.WriteEntity(convertToListResult(&filteredAppVersions, req)) + resp.WriteEntity(k8suitl.ConvertToListResult(&filteredAppVersions, req)) } diff --git a/pkg/kapis/application/v2/handler_category.go b/pkg/kapis/application/v2/handler_category.go index 55dec2f04..f3d69b2ef 100644 --- a/pkg/kapis/application/v2/handler_category.go +++ b/pkg/kapis/application/v2/handler_category.go @@ -8,6 +8,8 @@ package v2 import ( "fmt" + k8suitl "kubesphere.io/kubesphere/pkg/utils/k8sutil" + "github.com/emicklei/go-restful/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog/v2" @@ -99,5 +101,5 @@ func (h *appHandler) ListCategories(req *restful.Request, resp *restful.Response if requestDone(err, resp) { return } - resp.WriteEntity(convertToListResult(cList, req)) + resp.WriteEntity(k8suitl.ConvertToListResult(cList, req)) } diff --git a/pkg/kapis/application/v2/handler_repo.go b/pkg/kapis/application/v2/handler_repo.go index 7cdb79643..052af6fff 100644 --- a/pkg/kapis/application/v2/handler_repo.go +++ b/pkg/kapis/application/v2/handler_repo.go @@ -9,6 +9,8 @@ import ( "fmt" "net/url" + k8suitl "kubesphere.io/kubesphere/pkg/utils/k8sutil" + "kubesphere.io/kubesphere/pkg/simple/client/application" "kubesphere.io/kubesphere/pkg/api" @@ -115,6 +117,24 @@ func (h *appHandler) DeleteRepo(req *restful.Request, resp *restful.Response) { resp.WriteEntity(errors.None) } +func (h *appHandler) ManualSync(req *restful.Request, resp *restful.Response) { + repoId := req.PathParameter("repo") + + key := runtimeclient.ObjectKey{Name: repoId} + repo := &appv2.Repo{} + err := h.client.Get(req.Request.Context(), key, repo) + if requestDone(err, resp) { + return + } + repo.Status.State = appv2.StatusManualTrigger + err = h.client.Status().Update(req.Request.Context(), repo) + if err != nil { + api.HandleInternalError(resp, nil, err) + return + } + resp.WriteEntity(errors.None) +} + func (h *appHandler) DescribeRepo(req *restful.Request, resp *restful.Response) { repoId := req.PathParameter("repo") @@ -146,7 +166,7 @@ func (h *appHandler) ListRepos(req *restful.Request, resp *restful.Response) { filteredList.Items = append(filteredList.Items, repo) } - resp.WriteEntity(convertToListResult(filteredList, req)) + resp.WriteEntity(k8suitl.ConvertToListResult(filteredList, req)) } func (h *appHandler) ListRepoEvents(req *restful.Request, resp *restful.Response) { @@ -163,5 +183,5 @@ func (h *appHandler) ListRepoEvents(req *restful.Request, resp *restful.Response return } - resp.WriteEntity(convertToListResult(&list, req)) + resp.WriteEntity(k8suitl.ConvertToListResult(&list, req)) } diff --git a/pkg/kapis/application/v2/operator.go b/pkg/kapis/application/v2/operator.go index 5e95ed0ad..957d902fe 100644 --- a/pkg/kapis/application/v2/operator.go +++ b/pkg/kapis/application/v2/operator.go @@ -7,7 +7,8 @@ package v2 import ( "context" - "fmt" + + k8suitl "kubesphere.io/kubesphere/pkg/utils/k8sutil" "k8s.io/apimachinery/pkg/labels" @@ -17,7 +18,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" appv2 "kubesphere.io/api/application/v2" - "sigs.k8s.io/controller-runtime/pkg/client" "kubesphere.io/kubesphere/pkg/api" ) @@ -37,7 +37,7 @@ func (h *appHandler) AppCrList(req *restful.Request, resp *restful.Response) { return } opts.LabelSelector = labelSelector.String() - _, dynamicClient, _, err := h.getCluster(clusterName) + _, dynamicClient, _, err := h.getCluster(req, clusterName) if err != nil { api.HandleInternalError(resp, nil, err) return @@ -47,41 +47,27 @@ func (h *appHandler) AppCrList(req *restful.Request, resp *restful.Response) { api.HandleInternalError(resp, nil, err) return } - resp.WriteEntity(convertToListResult(list, req)) -} -func checkPermissions(gvr schema.GroupVersionResource, app appv2.Application) (allow bool) { - for _, i := range app.Spec.Resources { - if gvr.Resource == i.Resource { - allow = true - break - } - } - return allow + resp.WriteEntity(k8suitl.ConvertToListResult(list, req)) } + func (h *appHandler) CreateOrUpdateCR(req *restful.Request, resp *restful.Response) { gvr := schema.GroupVersionResource{ Group: req.QueryParameter("group"), Version: req.QueryParameter("version"), Resource: req.QueryParameter("resource"), } - appID := req.QueryParameter("app") - app := appv2.Application{} - err := h.client.Get(req.Request.Context(), client.ObjectKey{Name: appID}, &app) - if err != nil { - api.HandleInternalError(resp, nil, err) - return - } - allow := checkPermissions(gvr, app) - if !allow { - api.HandleForbidden(resp, nil, fmt.Errorf("resource %s not allow", gvr.Resource)) - return - } obj := unstructured.Unstructured{} - err = req.ReadEntity(&obj) + err := req.ReadEntity(&obj) if err != nil { api.HandleBadRequest(resp, nil, err) return } + lbs := obj.GetLabels() + if lbs == nil { + lbs = make(map[string]string) + } + lbs[appv2.AppReleaseReferenceLabelKey] = req.PathParameter("application") + obj.SetLabels(lbs) js, err := obj.MarshalJSON() if err != nil { @@ -89,7 +75,7 @@ func (h *appHandler) CreateOrUpdateCR(req *restful.Request, resp *restful.Respon return } clusterName := req.QueryParameter("cluster") - _, dynamicClient, _, err := h.getCluster(clusterName) + _, dynamicClient, _, err := h.getCluster(req, clusterName) if err != nil { api.HandleInternalError(resp, nil, err) return @@ -115,7 +101,7 @@ func (h *appHandler) DescribeAppCr(req *restful.Request, resp *restful.Response) Resource: req.QueryParameter("resource"), } clusterName := req.QueryParameter("cluster") - _, dynamicClient, _, err := h.getCluster(clusterName) + _, dynamicClient, _, err := h.getCluster(req, clusterName) if err != nil { api.HandleInternalError(resp, nil, err) return @@ -138,7 +124,7 @@ func (h *appHandler) DeleteAppCr(req *restful.Request, resp *restful.Response) { Resource: req.QueryParameter("resource"), } clusterName := req.QueryParameter("cluster") - _, dynamicClient, _, err := h.getCluster(clusterName) + _, dynamicClient, _, err := h.getCluster(req, clusterName) if err != nil { api.HandleInternalError(resp, nil, err) return diff --git a/pkg/kapis/application/v2/register.go b/pkg/kapis/application/v2/register.go index 337bf16e5..4f4aa31b6 100644 --- a/pkg/kapis/application/v2/register.go +++ b/pkg/kapis/application/v2/register.go @@ -66,6 +66,7 @@ func (h *appHandler) AddToContainer(c *restful.Container) (err error) { {Route: "/repos", Func: h.CreateOrUpdateRepo, Method: ws.POST, Workspace: true}, {Route: "/repos/{repo}", Func: h.CreateOrUpdateRepo, Method: ws.PATCH, Workspace: true}, {Route: "/repos/{repo}", Func: h.DeleteRepo, Method: ws.DELETE, Workspace: true}, + {Route: "/repos/{repo}/action", Func: h.ManualSync, Method: ws.POST, Workspace: true}, {Route: "/repos/{repo}", Func: h.DescribeRepo, Method: ws.GET, Workspace: true}, {Route: "/repos/{repo}/events", Func: h.ListRepoEvents, Method: ws.GET, Workspace: true}, {Route: "/apps/{app}/action", Func: h.DoAppAction, Method: ws.POST}, @@ -98,10 +99,10 @@ func (h *appHandler) AddToContainer(c *restful.Container) (err error) { {Route: "/attachments/{attachment}", Func: h.DescribeAttachment, Method: ws.GET, Workspace: true}, {Route: "/attachments/{attachment}", Func: h.DeleteAttachments, Method: ws.DELETE, Workspace: true}, {Route: "/apps/{app}/examplecr/{name}", Func: h.exampleCr, Method: ws.GET, Workspace: true}, - {Route: "/apps/{app}/cr", Func: h.AppCrList, Method: ws.GET, Workspace: true, Namespace: true}, - {Route: "/cr", Func: h.CreateOrUpdateCR, Method: ws.POST, Workspace: true, Namespace: true}, - {Route: "/cr/{crname}", Func: h.DescribeAppCr, Method: ws.GET, Workspace: true, Namespace: true}, - {Route: "/cr/{crname}", Func: h.DeleteAppCr, Method: ws.DELETE, Workspace: true, Namespace: true}, + {Route: "/applications/{application}/cr", Func: h.AppCrList, Method: ws.GET, Workspace: true, Namespace: true}, + {Route: "/applications/{application}/cr", Func: h.CreateOrUpdateCR, Method: ws.POST, Workspace: true, Namespace: true}, + {Route: "/applications/{application}/cr/{crname}", Func: h.DescribeAppCr, Method: ws.GET, Workspace: true, Namespace: true}, + {Route: "/applications/{application}/cr/{crname}", Func: h.DeleteAppCr, Method: ws.DELETE, Workspace: true, Namespace: true}, } for _, info := range funcInfoList { builder := info.Method(info.Route).To(info.Func).Doc(info.Doc) diff --git a/pkg/kapis/application/v2/utils.go b/pkg/kapis/application/v2/utils.go index 6a30eaf0c..6bebd746b 100644 --- a/pkg/kapis/application/v2/utils.go +++ b/pkg/kapis/application/v2/utils.go @@ -12,7 +12,12 @@ import ( "io" "strings" + "github.com/emicklei/go-restful/v3" + + "kubesphere.io/kubesphere/pkg/apiserver/request" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/klog/v2" @@ -31,9 +36,9 @@ const ( Status = "status" ) -func parseRequest(createRequest application.AppRequest) (appRequest application.AppRequest, err error) { +func parseRequest(createRequest application.AppRequest, validate bool) (appRequest application.AppRequest, err error) { if createRequest.AppType == appv2.AppTypeHelm { - req, err := parseHelmRequest(createRequest) + req, err := parseHelmRequest(createRequest, validate) return req, err } _, err = application.ReadYaml(createRequest.Package) @@ -41,7 +46,7 @@ func parseRequest(createRequest application.AppRequest) (appRequest application. return createRequest, err } -func parseHelmRequest(createRequest application.AppRequest) (req application.AppRequest, err error) { +func parseHelmRequest(createRequest application.AppRequest, validate bool) (req application.AppRequest, err error) { if createRequest.Package == nil || len(createRequest.Package) == 0 { return req, errors.New("package is empty") } @@ -49,7 +54,21 @@ func parseHelmRequest(createRequest application.AppRequest) (req application.App if err != nil { return createRequest, err } + if validate { + createRequest, err = getCrdInfo(createRequest, chartPack) + if err != nil { + klog.Errorf("failed to get crd info from %s: %v", chartPack.Metadata.Name, err) + return createRequest, err + } + } + shortName := application.GenerateShortNameMD5Hash(chartPack.Metadata.Name) + fillEmptyFields(&createRequest, chartPack, shortName) + + return createRequest, nil +} + +func getCrdInfo(createRequest application.AppRequest, chartPack *chart.Chart) (application.AppRequest, error) { crdFiles := chartPack.CRDObjects() for _, i := range crdFiles { dataList, err := readYaml(i.File.Data) @@ -83,10 +102,6 @@ func parseHelmRequest(createRequest application.AppRequest) (req application.App createRequest.Resources = append(createRequest.Resources, ins) } } - - shortName := application.GenerateShortNameMD5Hash(chartPack.Metadata.Name) - fillEmptyFields(&createRequest, chartPack, shortName) - return createRequest, nil } @@ -144,7 +159,7 @@ func fillEmptyFields(createRequest *application.AppRequest, chartPack *chart.Cha } } -func (h *appHandler) getCluster(clusterName string) (runtimeclient.Client, *dynamic.DynamicClient, *clusterv1alpha1.Cluster, error) { +func (h *appHandler) getCluster(req *restful.Request, clusterName string) (runtimeclient.Client, *dynamic.DynamicClient, *clusterv1alpha1.Cluster, error) { klog.Infof("get cluster %s", clusterName) runtimeClient, err := h.clusterClient.GetRuntimeClient(clusterName) if err != nil { @@ -154,7 +169,16 @@ func (h *appHandler) getCluster(clusterName string) (runtimeclient.Client, *dyna if err != nil { return nil, nil, nil, err } - dynamicClient, err := dynamic.NewForConfig(clusterClient.RestConfig) + + userInfo, _ := request.UserFrom(req.Request.Context()) + user := "" + if userInfo != nil { + user = userInfo.GetName() + } + conf := clusterClient.RestConfig + conf.Impersonate.UserName = user + + dynamicClient, err := dynamic.NewForConfig(conf) if err != nil { return nil, nil, nil, err } diff --git a/pkg/kapis/workloadtemplate/v1alpha1/handler.go b/pkg/kapis/workloadtemplate/v1alpha1/handler.go new file mode 100644 index 000000000..e1b174f49 --- /dev/null +++ b/pkg/kapis/workloadtemplate/v1alpha1/handler.go @@ -0,0 +1,203 @@ +package v1alpha1 + +import ( + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + + "k8s.io/apiserver/pkg/authentication/user" + tenantv1beta1 "kubesphere.io/api/tenant/v1beta1" + + "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer" + + "k8s.io/klog/v2" + + "kubesphere.io/kubesphere/pkg/apiserver/request" + "kubesphere.io/kubesphere/pkg/utils/stringutils" + + "github.com/emicklei/go-restful/v3" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "kubesphere.io/kubesphere/pkg/api" + "kubesphere.io/kubesphere/pkg/apiserver/query" + "kubesphere.io/kubesphere/pkg/server/errors" + k8suitl "kubesphere.io/kubesphere/pkg/utils/k8sutil" +) + +func (h *templateHandler) listWorkloadTemplate(req *restful.Request, resp *restful.Response) { + secretList := corev1.SecretList{} + requirements, _ := labels.SelectorFromSet(map[string]string{SchemeGroupVersion.Group: "true"}).Requirements() + userSelector := query.ParseQueryParameter(req).Selector() + combinedSelector := labels.NewSelector() + combinedSelector = combinedSelector.Add(requirements...) + if userSelector != nil { + userRequirements, _ := userSelector.Requirements() + combinedSelector = combinedSelector.Add(userRequirements...) + } + opts := []client.ListOption{ + client.MatchingLabelsSelector{Selector: combinedSelector}, + } + namespace := req.PathParameter("namespace") + if namespace != "" { + opts = append(opts, client.InNamespace(namespace)) + } + err := h.client.List(req.Request.Context(), &secretList, opts...) + if err != nil { + api.HandleError(resp, req, err) + return + } + workspace := req.PathParameter("workspace") + if workspace == "" { + resp.WriteEntity(k8suitl.ConvertToListResult(&secretList, req)) + return + } + + user, ok := request.UserFrom(req.Request.Context()) + if !ok { + err := fmt.Errorf("cannot obtain user info") + klog.Errorln(err) + api.HandleForbidden(resp, nil, err) + return + } + + filteredList, err := h.FilterByPermissions(workspace, user, secretList) + if err != nil { + api.HandleError(resp, req, err) + return + } + + resp.WriteEntity(k8suitl.ConvertToListResult(filteredList, req)) +} + +func (h *templateHandler) FilterByPermissions(workspace string, user user.Info, secretList corev1.SecretList) (*corev1.SecretList, error) { + + listNS := authorizer.AttributesRecord{ + User: user, + Verb: "list", + Workspace: workspace, + Resource: "namespaces", + ResourceRequest: true, + ResourceScope: request.WorkspaceScope, + } + + decision, _, err := h.authorizer.Authorize(listNS) + if err != nil { + klog.Error(err) + return nil, err + } + var namespaceList []string + if decision == authorizer.DecisionAllow { + queryParam := query.New() + queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1beta1.WorkspaceLabel, workspace)) + result, err := h.resourceGetter.List("namespaces", "", queryParam) + if err != nil { + klog.Error(err) + return nil, err + } + + for _, item := range result.Items { + ns := item.(*corev1.Namespace) + listWorkLoadTemplate := authorizer.AttributesRecord{ + User: user, + Verb: "list", + Namespace: ns.Name, + Resource: "workloadtemplates", + ResourceRequest: true, + ResourceScope: request.NamespaceScope, + } + decision, _, err = h.authorizer.Authorize(listWorkLoadTemplate) + if err != nil { + klog.Error(err) + return nil, err + } + if decision == authorizer.DecisionAllow { + namespaceList = append(namespaceList, ns.Name) + } else { + klog.Infof("user %s has no permission to list workloadtemplate in namespace %s", user.GetName(), ns.Name) + } + } + } + + filteredList := &corev1.SecretList{} + for _, item := range secretList.Items { + if !stringutils.StringIn(item.Namespace, namespaceList) { + continue + } + filteredList.Items = append(filteredList.Items, item) + } + return filteredList, nil +} + +func (h *templateHandler) applyWorkloadTemplate(req *restful.Request, resp *restful.Response) { + secret := &corev1.Secret{} + err := req.ReadEntity(secret) + if err != nil { + api.HandleError(resp, req, err) + return + } + if req.PathParameter("workloadtemplate") == "" { + // create new + ns := req.PathParameter("namespace") + err = h.client.Get(req.Request.Context(), runtimeclient.ObjectKey{Name: secret.Name, Namespace: ns}, secret) + if err != nil && !apierrors.IsNotFound(err) { + api.HandleError(resp, req, err) + return + } + if err == nil { + api.HandleConflict(resp, req, fmt.Errorf("workloadtemplate %s already exists", secret.Name)) + return + } + } + + newSecret := &corev1.Secret{} + newSecret.Name = secret.Name + newSecret.Namespace = req.PathParameter("namespace") + mutateFn := func() error { + newSecret.Annotations = secret.Annotations + if secret.Labels == nil { + secret.Labels = make(map[string]string) + } + newSecret.Labels = secret.Labels + newSecret.Labels[SchemeGroupVersion.Group] = "true" + newSecret.StringData = secret.StringData + newSecret.Type = corev1.SecretType(fmt.Sprintf("%s/%s", SchemeGroupVersion.Group, "workloadtemplate")) + return nil + } + _, err = controllerutil.CreateOrUpdate(req.Request.Context(), h.client, newSecret, mutateFn) + if err != nil { + api.HandleError(resp, req, err) + return + } + newSecret.SetManagedFields(nil) + resp.WriteAsJson(newSecret) +} + +func (h *templateHandler) deleteWorkloadTemplate(req *restful.Request, resp *restful.Response) { + name := req.PathParameter("workloadtemplate") + secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: name}} + secret.Namespace = req.PathParameter("namespace") + err := h.client.Delete(req.Request.Context(), secret) + if err != nil { + api.HandleError(resp, req, err) + return + } + resp.WriteEntity(errors.None) +} + +func (h *templateHandler) getWorkloadTemplate(req *restful.Request, resp *restful.Response) { + name := req.PathParameter("workloadtemplate") + secret := &corev1.Secret{} + ns := req.PathParameter("namespace") + err := h.client.Get(req.Request.Context(), runtimeclient.ObjectKey{Name: name, Namespace: ns}, secret) + if err != nil { + api.HandleError(resp, req, err) + return + } + secret.SetManagedFields(nil) + resp.WriteAsJson(secret) +} diff --git a/pkg/kapis/workloadtemplate/v1alpha1/register.go b/pkg/kapis/workloadtemplate/v1alpha1/register.go new file mode 100644 index 000000000..85805d283 --- /dev/null +++ b/pkg/kapis/workloadtemplate/v1alpha1/register.go @@ -0,0 +1,89 @@ +package v1alpha1 + +import ( + "github.com/Masterminds/semver/v3" + + "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer" + resourcesv1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource" + + "github.com/emicklei/go-restful/v3" + "k8s.io/apimachinery/pkg/runtime/schema" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" + + "kubesphere.io/kubesphere/pkg/apiserver/rest" + "kubesphere.io/kubesphere/pkg/apiserver/runtime" +) + +var ( + SchemeGroupVersion = schema.GroupVersion{Group: "workloadtemplate.kubesphere.io", Version: "v1alpha1"} +) + +type templateHandler struct { + client runtimeclient.Client + authorizer authorizer.Authorizer + resourceGetter *resourcesv1alpha3.Getter +} + +func NewHandler(cacheClient runtimeclient.Client, k8sVersion *semver.Version, authorizer authorizer.Authorizer) rest.Handler { + handler := &templateHandler{ + client: cacheClient, + authorizer: authorizer, + resourceGetter: resourcesv1alpha3.NewResourceGetter(cacheClient, k8sVersion), + } + return handler +} +func (h *templateHandler) AddToContainer(c *restful.Container) (err error) { + ws := runtime.NewWebService(SchemeGroupVersion) + + ws.Route(ws.GET("/workloadtemplates"). + To(h.listWorkloadTemplate). + Doc("List workload templates"). + Notes("List workload templates."). + Operation("listWorkloadTemplate")) + ws.Route(ws.GET("/workspaces/{workspace}/workloadtemplates"). + To(h.listWorkloadTemplate). + Doc("List workload templates in a workspace"). + Notes("List workload templates in a workspace."). + Operation("listWorkloadTemplate"). + Param(ws.PathParameter("workspace", "workspace"))) + ws.Route(ws.GET("/namespaces/{namespace}/workloadtemplates"). + To(h.listWorkloadTemplate). + Doc("List workload templates in a namespace"). + Notes("List workload templates in a namespace."). + Operation("listWorkloadTemplate"). + Param(ws.PathParameter("namespace", "namespace"))) + + ws.Route(ws.POST("/namespaces/{namespace}/workloadtemplates"). + To(h.applyWorkloadTemplate). + Doc("Apply a workload template in a namespace"). + Notes("Apply a workload template in a namespace."). + Operation("applyWorkloadTemplate"). + Param(ws.PathParameter("namespace", "namespace"))) + + ws.Route(ws.PUT("/namespaces/{namespace}/workloadtemplates/{workloadtemplate}"). + To(h.applyWorkloadTemplate). + Doc("Update a workload template"). + Notes("Update a workload template in a namespace."). + Operation("applyWorkloadTemplate"). + Param(ws.PathParameter("namespace", "namespace")). + Param(ws.PathParameter("workloadtemplate", "workloadtemplate"))) + + ws.Route(ws.DELETE("/namespaces/{namespace}/workloadtemplates/{workloadtemplate}"). + To(h.deleteWorkloadTemplate). + Doc("Delete a workload template in a namespace"). + Notes("List workload templates in a namespace."). + Operation("deleteWorkloadTemplate"). + Param(ws.PathParameter("namespace", "namespace")). + Param(ws.PathParameter("workloadtemplate", "workloadtemplate"))) + + ws.Route(ws.GET("/namespaces/{namespace}/workloadtemplates/{workloadtemplate}"). + To(h.getWorkloadTemplate). + Doc("Get a workload template in a namespace"). + Notes("Get a workload template in a namespace."). + Operation("getWorkloadTemplate"). + Param(ws.PathParameter("namespace", "namespace")). + Param(ws.PathParameter("workloadtemplate", "workloadtemplate"))) + + c.Add(ws) + return nil +} diff --git a/pkg/simple/client/application/helper.go b/pkg/simple/client/application/helper.go index 8392bbdbf..6168912bc 100644 --- a/pkg/simple/client/application/helper.go +++ b/pkg/simple/client/application/helper.go @@ -91,6 +91,7 @@ type AppRequest struct { Abstraction string `json:"abstraction,omitempty"` Attachments []string `json:"attachments,omitempty"` FromRepo bool `json:"fromRepo,omitempty"` + HasCrd string `json:"hasCrd,omitempty"` Resources []appv2.GroupVersionResource `json:"resources,omitempty"` } @@ -123,6 +124,7 @@ func CreateOrUpdateApp(client runtimeclient.Client, vRequests []AppRequest, cmSt AppType: request.AppType, Abstraction: request.Abstraction, Attachments: request.Attachments, + Resources: request.Resources, } if len(owns) > 0 { app.OwnerReferences = owns @@ -134,6 +136,7 @@ func CreateOrUpdateApp(client runtimeclient.Client, vRequests []AppRequest, cmSt } labels[appv2.RepoIDLabelKey] = request.RepoName labels[appv2.AppTypeLabelKey] = request.AppType + labels[appv2.HasCrdLabelKey] = request.HasCrd if request.CategoryName != "" { labels[appv2.AppCategoryNameKey] = request.CategoryName diff --git a/pkg/utils/k8sutil/k8sutil.go b/pkg/utils/k8sutil/k8sutil.go index 9749275b1..70be5505d 100644 --- a/pkg/utils/k8sutil/k8sutil.go +++ b/pkg/utils/k8sutil/k8sutil.go @@ -6,10 +6,17 @@ package k8sutil import ( + "github.com/emicklei/go-restful/v3" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" tenantv1beta1 "kubesphere.io/api/tenant/v1beta1" + + "kubesphere.io/kubesphere/pkg/api" + "kubesphere.io/kubesphere/pkg/apiserver/query" + resv1beta1 "kubesphere.io/kubesphere/pkg/models/resources/v1beta1" ) // IsControlledBy returns whether the ownerReferences contains the specified resource kind @@ -78,3 +85,23 @@ func GetObjectMeta(obj metav1.Object) metav1.ObjectMeta { ManagedFields: obj.GetManagedFields(), } } + +func ConvertToListResult(obj runtime.Object, req *restful.Request) (listResult api.ListResult) { + _ = meta.EachListItem(obj, omitManagedFields) + queryParams := query.ParseQueryParameter(req) + list, _ := meta.ExtractList(obj) + items, _, totalCount := resv1beta1.DefaultList(list, queryParams, resv1beta1.DefaultCompare, resv1beta1.DefaultFilter) + + listResult.Items = items + listResult.TotalItems = totalCount + + return listResult +} +func omitManagedFields(o runtime.Object) error { + a, err := meta.Accessor(o) + if err != nil { + return err + } + a.SetManagedFields(nil) + return nil +} diff --git a/staging/src/kubesphere.io/api/application/v2/constants.go b/staging/src/kubesphere.io/api/application/v2/constants.go index 1f14b79a4..4099e27d6 100644 --- a/staging/src/kubesphere.io/api/application/v2/constants.go +++ b/staging/src/kubesphere.io/api/application/v2/constants.go @@ -7,6 +7,7 @@ const ( AppOriginalNameLabelKey = "application.kubesphere.io/app-originalName" AppVersionIDLabelKey = "application.kubesphere.io/appversion-id" AppTypeLabelKey = "application.kubesphere.io/app-type" + HasCrdLabelKey = "application.kubesphere.io/hascrd" AppStoreLabelKey = "application.kubesphere.io/app-store" TimeoutRecheck = "application.kubesphere.io/timeout-recheck" AppCategoryNameKey = "application.kubesphere.io/app-category-name" @@ -18,6 +19,7 @@ const ( StatusSuccessful = "successful" StatusCreating = "creating" StatusSyncing = "syncing" + StatusManualTrigger = "manualTrigger" StatusDeleting = "deleting" StatusUpgrading = "upgrading" StatusClusterDeleted = "clusterDeleted" diff --git a/staging/src/kubesphere.io/api/application/v2/repo_types.go b/staging/src/kubesphere.io/api/application/v2/repo_types.go index 568f7d077..dfa5a09b7 100644 --- a/staging/src/kubesphere.io/api/application/v2/repo_types.go +++ b/staging/src/kubesphere.io/api/application/v2/repo_types.go @@ -26,7 +26,7 @@ type RepoSpec struct { Url string `json:"url"` Credential RepoCredential `json:"credential,omitempty"` Description string `json:"description,omitempty"` - SyncPeriod int `json:"syncPeriod,omitempty"` + SyncPeriod *int `json:"syncPeriod"` } // RepoStatus defines the observed state of Repo diff --git a/staging/src/kubesphere.io/api/application/v2/zz_generated.deepcopy.go b/staging/src/kubesphere.io/api/application/v2/zz_generated.deepcopy.go index ae9096a1e..b26842421 100644 --- a/staging/src/kubesphere.io/api/application/v2/zz_generated.deepcopy.go +++ b/staging/src/kubesphere.io/api/application/v2/zz_generated.deepcopy.go @@ -542,6 +542,11 @@ func (in *RepoList) DeepCopyObject() runtime.Object { func (in *RepoSpec) DeepCopyInto(out *RepoSpec) { *out = *in in.Credential.DeepCopyInto(&out.Credential) + if in.SyncPeriod != nil { + in, out := &in.SyncPeriod, &out.SyncPeriod + *out = new(int) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RepoSpec.