Merge pull request #1989 from wansir/dev

add iam crd
This commit is contained in:
KubeSphere CI Bot
2020-04-12 21:21:21 +08:00
committed by GitHub
908 changed files with 8121 additions and 140563 deletions

View File

@@ -35,6 +35,7 @@ import (
controllerconfig "kubesphere.io/kubesphere/pkg/apiserver/config"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
"kubesphere.io/kubesphere/pkg/controller/namespace"
"kubesphere.io/kubesphere/pkg/controller/user"
"kubesphere.io/kubesphere/pkg/controller/workspace"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
@@ -43,6 +44,7 @@ import (
"os"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)
func NewControllerManagerCommand() *cobra.Command {
@@ -151,6 +153,14 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{})
// Start cache data after all informer is registered
informerFactory.Start(stopCh)
// Setup webhooks
klog.Info("setting up webhook server")
hookServer := mgr.GetWebhookServer()
klog.Info("registering webhooks to the webhook server")
hookServer.Register("/mutating-encrypt-password-iam-kubesphere-io-v1alpha2-user", &webhook.Admission{Handler: &user.PasswordCipher{Client: mgr.GetClient()}})
hookServer.Register("/validate-email-iam-kubesphere-io-v1alpha2-user", &webhook.Admission{Handler: &user.EmailValidator{Client: mgr.GetClient()}})
klog.V(0).Info("Starting the controllers.")
if err = mgr.Start(stopCh); err != nil {
klog.Fatalf("unable to run the manager: %v", err)

View File

@@ -0,0 +1,58 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: (devel)
creationTimestamp: null
name: policyrules.iam.kubesphere.io
spec:
additionalPrinterColumns:
- JSONPath: .scope
name: Scope
type: string
group: iam.kubesphere.io
names:
categories:
- iam
kind: PolicyRule
listKind: PolicyRuleList
plural: policyrules
singular: policyrule
scope: Cluster
subresources: {}
validation:
openAPIV3Schema:
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
rego:
type: string
scope:
type: string
required:
- rego
- scope
type: object
version: v1alpha2
versions:
- name: v1alpha2
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -0,0 +1,104 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: (devel)
creationTimestamp: null
name: rolebindings.iam.kubesphere.io
spec:
additionalPrinterColumns:
- JSONPath: .scope
name: Scope
type: string
- JSONPath: .roleRef.name
name: RoleRef
type: string
- JSONPath: .subjects[*].name
name: Subjects
type: string
group: iam.kubesphere.io
names:
categories:
- iam
kind: RoleBinding
listKind: RoleBindingList
plural: rolebindings
singular: rolebinding
scope: Cluster
subresources: {}
validation:
openAPIV3Schema:
description: RoleBinding is the Schema for the rolebindings API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
roleRef:
description: RoleRef contains information that points to the role being
used
properties:
apiGroup:
description: APIGroup is the group for the resource being referenced
type: string
kind:
description: Kind is the type of resource being referenced
type: string
name:
description: Name is the name of resource being referenced
type: string
required:
- apiGroup
- kind
- name
type: object
scope:
type: string
subjects:
description: Subjects holds references to the users the role applies to.
items:
description: or a value for non-objects such as user and group names.
properties:
apiGroup:
description: APIGroup holds the API group of the referenced subject.
type: string
kind:
description: Kind of object being referenced. Values defined by this
API group are "User", "Group", and "ServiceAccount". If the Authorizer
does not recognized the kind value, the Authorizer should report
an error.
type: string
name:
description: Name of the object being referenced.
type: string
required:
- apiGroup
- kind
- name
type: object
type: array
required:
- roleRef
- scope
type: object
version: v1alpha2
versions:
- name: v1alpha2
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

87
config/crds/iam.kubesphere.io_roles.yaml generated Normal file
View File

@@ -0,0 +1,87 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: (devel)
creationTimestamp: null
name: roles.iam.kubesphere.io
spec:
additionalPrinterColumns:
- JSONPath: .target.scope
name: Scope
type: string
- JSONPath: .target.name
name: Target
type: string
group: iam.kubesphere.io
names:
categories:
- iam
kind: Role
listKind: RoleList
plural: roles
singular: role
scope: Cluster
subresources: {}
validation:
openAPIV3Schema:
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
rules:
items:
description: RuleRef contains information that points to the role being
used
properties:
apiGroup:
description: APIGroup is the group for the resource being referenced
type: string
kind:
description: Kind is the type of resource being referenced
type: string
name:
description: Name is the name of resource being referenced
type: string
required:
- apiGroup
- kind
- name
type: object
type: array
target:
properties:
name:
type: string
scope:
type: string
required:
- name
- scope
type: object
required:
- rules
- target
type: object
version: v1alpha2
versions:
- name: v1alpha2
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -0,0 +1,55 @@
apiVersion: iam.kubesphere.io/v1alpha2
kind: PolicyRule
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: always-allow
scope: Global
rego: 'package authz\ndefault allow = true'
---
apiVersion: iam.kubesphere.io/v1alpha2
kind: PolicyRule
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: always-deny
scope: Global
rego:
package authz
default allow = false
---
apiVersion: iam.kubesphere.io/v1alpha2
kind: PolicyRule
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: cluster-manage
scope: Global
rego:
package authz
default allow = false
allow {
input.Resource == 'clusters'
}
---
apiVersion: iam.kubesphere.io/v1alpha2
kind: PolicyRule
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: some-namespace-manage
scope: Namespace
rego:
package authz
default allow = false
allow {
input.Resource == 'clusters'
}

View File

@@ -0,0 +1,30 @@
apiVersion: iam.kubesphere.io/v1alpha2
kind: Role
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: cluster-admin
target:
scope: Global
name: ''
rules:
- apiGroup: iam.kubesphere.io/v1alpha2
kind: PolicyRule
name: always-allow
---
apiVersion: iam.kubesphere.io/v1alpha2
kind: Role
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: anonymous
target:
scope: Global
name: ''
rules:
- apiGroup: iam.kubesphere.io/v1alpha2
kind: PolicyRule
name: always-deny

View File

@@ -0,0 +1,15 @@
apiVersion: iam.kubesphere.io/v1alpha2
kind: RoleBinding
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: cluster-admin
scope: Global
roleRef:
apiGroup: iam.kubesphere.io/v1alpha2
kind: Role
name: cluster-admin
subjects:
- apiGroup: iam.kubesphere.io/v1alpha2
kind: User
name: admin

View File

@@ -6,4 +6,4 @@ metadata:
name: admin
spec:
email: admin@kubesphere.io
password: d41d8cd98f00b204e9800998ecf8427e
password: $2a$04$wr/XmTQ99uQpgi335xPyoOM08h34ZQk265pdqHMv5Yw6Xo2vfiO/6

55
config/webhook/iam.yaml Normal file
View File

@@ -0,0 +1,55 @@
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: kubesphere-iam-validator
webhooks:
- admissionReviewVersions:
- v1beta1
clientConfig:
caBundle: <caBundle>
service:
name: webhook-service
namespace: kubesphere-system
path: /validate-email-iam-kubesphere-io-v1alpha2-user
failurePolicy: Fail
name: vemail.iam.kubesphere.io
rules:
- apiGroups:
- iam.kubesphere.io
apiVersions:
- v1alpha2
operations:
- CREATE
- UPDATE
resources:
- users
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: kubesphere-iam-injector
webhooks:
- admissionReviewVersions:
- v1beta1
clientConfig:
caBundle: <caBundle>
service:
name: webhook-service
namespace: kubesphere-system
path: /mutating-encrypt-password-iam-kubesphere-io-v1alpha2-user
failurePolicy: Fail
name: mpassword.iam.kubesphere.io
reinvocationPolicy: Never
rules:
- apiGroups:
- iam.kubesphere.io
apiVersions:
- v1alpha2
operations:
- CREATE
- UPDATE
resources:
- users

22
go.mod
View File

@@ -15,7 +15,6 @@ require (
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
github.com/aws/aws-sdk-go v1.22.2
github.com/beevik/etree v1.1.0
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v1.4.2-0.20190822205725-ed20165a37b4
@@ -54,24 +53,19 @@ require (
github.com/json-iterator/go v1.1.8
github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/kiali/kiali v0.15.1-0.20191210080139-edbbad1ef779
github.com/klauspost/cpuid v1.2.1 // indirect
github.com/kubernetes-sigs/application v0.0.0-20191210100950-18cc93526ab4
github.com/kubesphere/sonargo v0.0.2
github.com/leodido/go-urn v1.1.0 // indirect
github.com/lib/pq v1.2.0 // indirect
github.com/lucas-clemente/quic-go v0.11.1 // indirect
github.com/mailru/easyjson v0.7.0 // indirect
github.com/mattn/go-sqlite3 v1.11.0 // indirect
github.com/mholt/caddy v1.0.0
github.com/mholt/certmagic v0.5.1 // indirect
github.com/miekg/dns v1.1.9 // indirect
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
github.com/onsi/ginkgo v1.8.0
github.com/onsi/gomega v1.5.0
github.com/open-policy-agent/opa v0.18.0
github.com/opencontainers/go-digest v1.0.0-rc1
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/openshift/api v3.9.0+incompatible // indirect
github.com/openshift/api v0.0.0-20180801171038-322a19404e37 // indirect
github.com/pkg/errors v0.8.1
github.com/projectcalico/libcalico-go v1.7.2-0.20191104213956-8f81e1e344ce
github.com/prometheus/common v0.4.0
@@ -82,23 +76,22 @@ require (
github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.4.0
github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
google.golang.org/grpc v1.23.1
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect
gopkg.in/src-d/go-git.v4 v4.11.0
gopkg.in/yaml.v2 v2.2.4
istio.io/api v0.0.0-20191111210003-35e06ef8d838
istio.io/client-go v0.0.0-20191113122552-9bd0ba57c3d2
k8s.io/api v0.0.0-20191114100352-16d7abae0d2a
k8s.io/api v0.18.0
k8s.io/apiextensions-apiserver v0.0.0-20191114105449-027877536833
k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb
k8s.io/apimachinery v0.18.0
k8s.io/apiserver v0.0.0-20191114103151-9ca1dc586682
k8s.io/client-go v0.0.0-20191114101535-6c5935290e33
k8s.io/code-generator v0.0.0-20191004115455-8e001e5d1894
k8s.io/code-generator v0.18.0
k8s.io/component-base v0.0.0-20191114102325-35a9586014f7
k8s.io/gengo v0.0.0-20191120174120-e74f70b9b27e // indirect
k8s.io/klog v1.0.0
@@ -301,7 +294,8 @@ replace (
github.com/open-policy-agent/opa => github.com/open-policy-agent/opa v0.18.0
github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.0-rc1
github.com/opencontainers/image-spec => github.com/opencontainers/image-spec v1.0.1
github.com/openshift/api => github.com/openshift/api v3.9.0+incompatible
github.com/openshift/api => github.com/openshift/api v0.0.0-20180801171038-322a19404e37
github.com/openshift/build-machinery-go => github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160
github.com/pborman/uuid => github.com/pborman/uuid v1.2.0
github.com/pelletier/go-buffruneio => github.com/pelletier/go-buffruneio v0.2.0
github.com/pelletier/go-toml => github.com/pelletier/go-toml v1.2.0

39
go.sum
View File

@@ -58,12 +58,8 @@ github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@@ -96,8 +92,6 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elastic/go-elasticsearch/v5 v5.6.1 h1:RnL2wcXepOT5SdoKMMO1j1OBX0vxHYbBtkQNL2E3xs4=
github.com/elastic/go-elasticsearch/v5 v5.6.1/go.mod h1:r7uV7HidpfkYh7D8SB4lkS13TNlNy3oa5GNmTZvuVqY=
github.com/elastic/go-elasticsearch/v6 v6.8.2 h1:rp5DGrd63V5c6nHLjF6QEXUpZSvs0+QM3ld7m9VhV2g=
@@ -133,8 +127,6 @@ github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-acme/lego v2.5.0+incompatible h1:5fNN9yRQfv8ymH3DSsxla+4aYeQt2IgfZqHKVnK8f0s=
github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
@@ -225,8 +217,6 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92Bcuy
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.6 h1:8p0pcgLlw2iuZVsdHdPaMUXFOA+6gDixcXbHEMzSyW8=
github.com/grpc-ecosystem/grpc-gateway v1.9.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
@@ -242,8 +232,6 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a h1:BcF8coBl0QFVhe8vAMMlD+CV8EISiu9MGKLoj6ZEyJA=
github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8=
github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc=
@@ -263,8 +251,6 @@ github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT
github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7 h1:SWlt7BoQNASbhTUD0Oy5yysI2seJ7vWuGUp///OM4TM=
github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7/go.mod h1:Y2SaZf2Rzd0pXkLVhLlCiAXFCLSXAIbTKDivVgff/AM=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
@@ -282,20 +268,14 @@ github.com/kubesphere/kiali v0.15.1-0.20191210080139-edbbad1ef779 h1:52StEbBn6dR
github.com/kubesphere/kiali v0.15.1-0.20191210080139-edbbad1ef779/go.mod h1:Y1EqeixoXkKkU8I+yvOfhdh21+8+etFE6wYOVT2XFdI=
github.com/kubesphere/sonargo v0.0.2 h1:hsSRE3sv3mkPcUAeSABdp7rtfcNW2zzeHXzFa01CTkU=
github.com/kubesphere/sonargo v0.0.2/go.mod h1:ww8n9ANlDXhX5PBZ18iaRnCgEkXN0GMml3/KZXOZ11w=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lucas-clemente/quic-go v0.11.1 h1:zasajC848Dqq/+WqfqBCkmPw+YHNe1MBts/z7y7nXf4=
github.com/lucas-clemente/quic-go v0.11.1/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
@@ -305,12 +285,6 @@ github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mholt/caddy v1.0.0 h1:KI6RPGih2GFzWRPG8s9clKK28Ns4ZlVMKR/v7mxq6+c=
github.com/mholt/caddy v1.0.0/go.mod h1:PzUpQ3yGCTuEuy0KSxEeB4TZOi3zBZ8BR/zY0RBP414=
github.com/mholt/certmagic v0.5.1 h1:8Pf6Hwwlh5sbT3nwn3ovXyXWxHCEM54wvfLzTrQ+UiM=
github.com/mholt/certmagic v0.5.1/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY=
github.com/miekg/dns v1.1.9 h1:OIdC9wT96RzuZMf2PfKRhFgsStHUUBZLM/lo1LqiM9E=
github.com/miekg/dns v1.1.9/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
@@ -327,10 +301,6 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks=
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
@@ -343,8 +313,11 @@ github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2i
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs=
github.com/openshift/api v3.9.0+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY=
github.com/openshift/api v0.0.0-20180801171038-322a19404e37 h1:05irGU4HK4IauGGDbsk+ZHrm1wOzMLYjMlfaiqMrBYc=
github.com/openshift/api v0.0.0-20180801171038-322a19404e37/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY=
github.com/openshift/api v0.0.0-20200331152225-585af27e34fd h1:f4iPC9iCf1an7qEWpEFvp/swsM79vvBRsJ2twU4D30s=
github.com/openshift/api v0.0.0-20200331152225-585af27e34fd/go.mod h1:RKMJ5CBnljLfnej+BJ/xnOWc3kZDvJUaIAEq2oKSPtE=
github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc=
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
@@ -492,8 +465,6 @@ gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvR
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/mcuadros/go-syslog.v2 v2.2.1 h1:60g8zx1BijSVSgLTzLCW9UC4/+i1Ih9jJ1DR5Tgp9vE=
gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=

View File

@@ -1,41 +0,0 @@
package iam
import (
"kubesphere.io/kubesphere/pkg/server/errors"
"time"
)
type User struct {
Name string `json:"username"`
UID string `json:"uid"`
Email string `json:"email"`
Lang string `json:"lang,omitempty"`
Description string `json:"description"`
CreateTime time.Time `json:"createTime"`
Groups []string `json:"groups,omitempty"`
Password string `json:"password,omitempty"`
}
func (u *User) GetName() string {
return u.Name
}
func (u *User) GetUID() string {
return u.UID
}
func (u *User) GetEmail() string {
return u.Email
}
func (u *User) Validate() error {
if u.Name == "" {
return errors.New("username can not be empty")
}
if u.Password == "" {
return errors.New("password can not be empty")
}
return nil
}

View File

@@ -6,8 +6,8 @@ import (
)
type ListResult struct {
Items []interface{} `json:"items,omitempty"`
TotalItems int `json:"totalItems,omitempty"`
Items []interface{} `json:"items"`
TotalItems int `json:"totalItems"`
}
type ResourceQuota struct {

View File

@@ -25,8 +25,9 @@ limitations under the License.
package v1alpha2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/runtime/scheme"
)
var (
@@ -34,7 +35,7 @@ var (
SchemeGroupVersion = schema.GroupVersion{Group: "iam.kubesphere.io", Version: "v1alpha2"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme is required by pkg/client/...
AddToScheme = SchemeBuilder.AddToScheme
@@ -44,3 +45,18 @@ var (
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// Adds the list of known types to the given scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&User{},
&Role{},
&RoleList{},
&RoleBinding{},
&RoleBindingList{},
&PolicyRule{},
&PolicyRuleList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}

View File

@@ -0,0 +1,257 @@
/*
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"
)
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:openapi-gen=true
// User is the Schema for the users API
// +kubebuilder:printcolumn:name="Email",type="string",JSONPath=".spec.email"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state"
// +kubebuilder:resource:categories="iam",scope="Cluster"
type User struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec UserSpec `json:"spec"`
// +optional
Status UserStatus `json:"status,omitempty"`
}
type FinalizerName string
// UserSpec defines the desired state of User
type UserSpec struct {
// Unique email address.
Email string `json:"email"`
// The preferred written or spoken language for the user.
// +optional
Lang string `json:"lang,omitempty"`
// Description of the user.
// +optional
Description string `json:"description,omitempty"`
// +optional
DisplayName string `json:"displayName,omitempty"`
// +optional
Groups []string `json:"groups,omitempty"`
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"`
}
type UserState string
// These are the valid phases of a user.
const (
// UserActive means the user is available.
UserActive UserState = "Active"
// UserDisabled means the user is disabled.
UserDisabled UserState = "Disabled"
)
// UserStatus defines the observed state of User
type UserStatus struct {
// The user status
// +optional
State UserState `json:"state,omitempty"`
// Represents the latest available observations of a namespace's current state.
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
Conditions []UserCondition `json:"conditions,omitempty"`
}
type UserCondition struct {
// Type of namespace controller condition.
Type UserConditionType `json:"type"`
// Status of the condition, one of True, False, Unknown.
Status ConditionStatus `json:"status"`
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
// +optional
Reason string `json:"reason,omitempty"`
// +optional
Message string `json:"message,omitempty"`
}
type UserConditionType string
// These are valid conditions of a user.
const (
// UserLoginFailure contains information about user login.
UserLoginFailure UserConditionType = "UserLoginFailure"
)
type ConditionStatus string
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
// can't decide if a resource is in the condition or not. In the future, we could add other
// intermediate conditions, e.g. ConditionDegraded.
const (
ConditionTrue ConditionStatus = "True"
ConditionFalse ConditionStatus = "False"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// UserList contains a list of User
type UserList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []User `json:"items"`
}
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:printcolumn:name="Scope",type="string",JSONPath=".target.scope"
// +kubebuilder:printcolumn:name="Target",type="string",JSONPath=".target.name"
// +kubebuilder:resource:categories="iam",scope="Cluster"
type Role struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Target Target `json:"target"`
Rules []RuleRef `json:"rules"`
}
type Target struct {
Scope Scope `json:"scope"`
Name string `json:"name"`
}
type Scope string
const (
GlobalScope Scope = "Global"
ClusterScope Scope = "Cluster"
WorkspaceScope Scope = "Workspace"
NamespaceScope Scope = "Namespace"
TargetAll = "*"
UserKind = "User"
PolicyRuleKind = "PolicyRule"
RoleKind = "Role"
RoleBindingKind = "RoleBinding"
)
// RuleRef contains information that points to the role being used
type RuleRef struct {
// APIGroup is the group for the resource being referenced
APIGroup string `json:"apiGroup"`
// Kind is the type of resource being referenced
Kind string `json:"kind"`
// Name is the name of resource being referenced
Name string `json:"name"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// RoleList contains a list of Role
type RoleList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Role `json:"items"`
}
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:printcolumn:name="Scope",type="string",JSONPath=".scope"
// +kubebuilder:resource:categories="iam",scope="Cluster"
type PolicyRule struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Scope Scope `json:"scope"`
Rego string `json:"rego"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// PolicyRuleList contains a list of PolicyRule
type PolicyRuleList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []PolicyRule `json:"items"`
}
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// RoleBinding is the Schema for the rolebindings API
// +kubebuilder:printcolumn:name="Scope",type="string",JSONPath=".scope"
// +kubebuilder:printcolumn:name="RoleRef",type="string",JSONPath=".roleRef.name"
// +kubebuilder:printcolumn:name="Subjects",type="string",JSONPath=".subjects[*].name"
// +kubebuilder:resource:categories="iam",scope="Cluster"
type RoleBinding struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Scope Scope `json:"scope"`
RoleRef RoleRef `json:"roleRef"`
// Subjects holds references to the users the role applies to.
// +optional
Subjects []Subject `json:"subjects,omitempty"`
}
// RoleRef contains information that points to the role being used
type RoleRef struct {
// APIGroup is the group for the resource being referenced
APIGroup string `json:"apiGroup"`
// Kind is the type of resource being referenced
Kind string `json:"kind"`
// Name is the name of resource being referenced
Name string `json:"name"`
}
// or a value for non-objects such as user and group names.
type Subject struct {
// Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount".
// If the Authorizer does not recognized the kind value, the Authorizer should report an error.
Kind string `json:"kind"`
// APIGroup holds the API group of the referenced subject.
APIGroup string `json:"apiGroup"`
// Name of the object being referenced.
Name string `json:"name"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// RoleBindingList contains a list of RoleBinding
type RoleBindingList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []RoleBinding `json:"items"`
}
type UserDetail struct {
*User
GlobalRole *Role `json:"globalRole"`
}

View File

@@ -1,130 +0,0 @@
/*
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"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +genclient:nonNamespaced
// +k8s:openapi-gen=true
// +kubebuilder:printcolumn:name="Email",type="string",JSONPath=".spec.email"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state"
// +kubebuilder:resource:categories="iam",scope="Cluster"
// User is the Schema for the users API
type User struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec UserSpec `json:"spec"`
// +optional
Status UserStatus `json:"status,omitempty"`
}
type FinalizerName string
// UserSpec defines the desired state of User
type UserSpec struct {
// Unique email address.
Email string `json:"email"`
// The preferred written or spoken language for the user.
// +optional
Lang string `json:"lang,omitempty"`
// Description of the user.
// +optional
Description string `json:"description,omitempty"`
// +optional
DisplayName string `json:"displayName,omitempty"`
// +optional
Groups []string `json:"groups,omitempty"`
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"`
}
type UserState string
// These are the valid phases of a user.
const (
// UserActive means the user is available.
UserActive UserState = "Active"
// UserDisabled means the user is disabled.
UserDisabled UserState = "Disabled"
)
// UserStatus defines the observed state of User
type UserStatus struct {
// The user status
// +optional
State UserState `json:"state,omitempty"`
// Represents the latest available observations of a namespace's current state.
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
Conditions []UserCondition `json:"conditions,omitempty"`
}
type UserCondition struct {
// Type of namespace controller condition.
Type UserConditionType `json:"type"`
// Status of the condition, one of True, False, Unknown.
Status ConditionStatus `json:"status"`
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
// +optional
Reason string `json:"reason,omitempty"`
// +optional
Message string `json:"message,omitempty"`
}
type UserConditionType string
// These are valid conditions of a user.
const (
// UserLoginFailure contains information about user login.
UserLoginFailure UserConditionType = "UserLoginFailure"
)
type ConditionStatus string
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
// can't decide if a resource is in the condition or not. In the future, we could add other
// intermediate conditions, e.g. ConditionDegraded.
const (
ConditionTrue ConditionStatus = "True"
ConditionFalse ConditionStatus = "False"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// UserList contains a list of User
type UserList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []User `json:"items"`
}
func init() {
SchemeBuilder.Register(&User{}, &UserList{})
}

View File

@@ -21,9 +21,252 @@ limitations under the License.
package v1alpha2
import (
runtime "k8s.io/apimachinery/pkg/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 *PolicyRule) DeepCopyInto(out *PolicyRule) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyRule.
func (in *PolicyRule) DeepCopy() *PolicyRule {
if in == nil {
return nil
}
out := new(PolicyRule)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PolicyRule) 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 *PolicyRuleList) DeepCopyInto(out *PolicyRuleList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]PolicyRule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyRuleList.
func (in *PolicyRuleList) DeepCopy() *PolicyRuleList {
if in == nil {
return nil
}
out := new(PolicyRuleList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PolicyRuleList) 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 *Role) DeepCopyInto(out *Role) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Target = in.Target
if in.Rules != nil {
in, out := &in.Rules, &out.Rules
*out = make([]RuleRef, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Role.
func (in *Role) DeepCopy() *Role {
if in == nil {
return nil
}
out := new(Role)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Role) 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 *RoleBinding) DeepCopyInto(out *RoleBinding) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.RoleRef = in.RoleRef
if in.Subjects != nil {
in, out := &in.Subjects, &out.Subjects
*out = make([]Subject, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBinding.
func (in *RoleBinding) DeepCopy() *RoleBinding {
if in == nil {
return nil
}
out := new(RoleBinding)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RoleBinding) 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 *RoleBindingList) DeepCopyInto(out *RoleBindingList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]RoleBinding, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleBindingList.
func (in *RoleBindingList) DeepCopy() *RoleBindingList {
if in == nil {
return nil
}
out := new(RoleBindingList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RoleBindingList) 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 *RoleList) DeepCopyInto(out *RoleList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Role, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleList.
func (in *RoleList) DeepCopy() *RoleList {
if in == nil {
return nil
}
out := new(RoleList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RoleList) 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 *RoleRef) DeepCopyInto(out *RoleRef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleRef.
func (in *RoleRef) DeepCopy() *RoleRef {
if in == nil {
return nil
}
out := new(RoleRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RuleRef) DeepCopyInto(out *RuleRef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleRef.
func (in *RuleRef) DeepCopy() *RuleRef {
if in == nil {
return nil
}
out := new(RuleRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Subject) DeepCopyInto(out *Subject) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subject.
func (in *Subject) DeepCopy() *Subject {
if in == nil {
return nil
}
out := new(Subject)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Target) DeepCopyInto(out *Target) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Target.
func (in *Target) DeepCopy() *Target {
if in == nil {
return nil
}
out := new(Target)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *User) DeepCopyInto(out *User) {
*out = *in
@@ -69,6 +312,31 @@ 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(Role)
(*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

View File

@@ -144,7 +144,10 @@ func (s *APIServer) installKubeSphereAPIs() {
urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory))
//urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database()))
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.Config.AuthenticationOptions))
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, im.NewOperator(s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory()),
am.NewAMOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory()),
s.Config.AuthenticationOptions))
urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient), s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
}
@@ -190,12 +193,12 @@ func (s *APIServer) buildHandlerChain() {
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
// union authorizers are ordered, don't change the order here
authorizers := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator()))
authorizers := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewAMOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory())))
handler = filters.WithAuthorization(handler, authorizers)
// authenticators are unordered
authn := unionauth.New(anonymous.NewAuthenticator(),
basictoken.New(basic.NewBasicAuthenticator(im.NewFakeOperator())),
basictoken.New(basic.NewBasicAuthenticator(im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory()))),
bearertoken.New(jwttoken.NewTokenAuthenticator(token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient))))
handler = filters.WithAuthentication(handler, authn)
handler = filters.WithRequestInfo(handler, requestInfoResolver)
@@ -275,6 +278,10 @@ func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error {
ksGVRs := []schema.GroupVersionResource{
{Group: "tenant.kubesphere.io", Version: "v1alpha1", Resource: "workspaces"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "users"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "roles"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "rolebindings"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "policyrules"},
{Group: "tower.kubesphere.io", Version: "v1alpha1", Resource: "agents"},
}

View File

@@ -51,7 +51,7 @@ func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username,
return &authenticator.Response{
User: &user.DefaultInfo{
Name: providedUser.GetName(),
UID: providedUser.GetUID(),
UID: string(providedUser.GetUID()),
Groups: []string{user.AllAuthenticated},
},
}, true, nil

View File

@@ -21,7 +21,7 @@ package token
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"kubesphere.io/kubesphere/pkg/api/iam"
"k8s.io/apiserver/pkg/authentication/user"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
@@ -69,7 +69,7 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
return nil, err
}
return &iam.User{Name: clm.Username, UID: clm.UID}, nil
return &user.DefaultInfo{Name: clm.Username, UID: clm.UID}, nil
}
func (s *jwtTokenIssuer) IssueTo(user User, expiresIn time.Duration) (string, error) {

View File

@@ -20,7 +20,7 @@ package token
import (
"github.com/google/go-cmp/cmp"
"kubesphere.io/kubesphere/pkg/api/iam"
"k8s.io/apiserver/pkg/authentication/user"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"testing"
@@ -48,7 +48,7 @@ func TestJwtTokenIssuer(t *testing.T) {
}
for _, testCase := range testCases {
user := &iam.User{
user := &user.DefaultInfo{
Name: testCase.name,
UID: testCase.uid,
}

View File

@@ -21,6 +21,9 @@ package authorizerfactory
import (
"context"
"github.com/open-policy-agent/opa/rego"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/models/iam/am"
)
@@ -29,115 +32,126 @@ type opaAuthorizer struct {
am am.AccessManagementInterface
}
const (
permissionUndefined = "permission undefined"
defaultRegoQuery = "data.authz.allow"
)
// Make decision by request attributes
func (o *opaAuthorizer) Authorize(attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
// Make decisions based on the authorization policy of different levels of roles
platformRole, err := o.am.GetPlatformRole(attr.GetUser().GetName())
// Error returned when an internal error occurs
// Reason must be returned when access is denied
globalRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.GlobalScope, "", attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check platform role policy rules
if authorized, reason, err = makeDecision(platformRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, err
if authorized, reason, err = o.makeDecision(globalRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, nil
}
// it's not in cluster resource, permission denied
if attr.GetCluster() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
clusterRole, err := o.am.GetClusterRole(attr.GetCluster(), attr.GetUser().GetName())
clusterRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.ClusterScope, attr.GetCluster(), attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check cluster role policy rules
if a, r, e := makeDecision(clusterRole, attr); a == authorizer.DecisionAllow {
return a, r, e
if authorized, reason, err := o.makeDecision(clusterRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, nil
} else if err != nil {
return authorizer.DecisionDeny, "", err
}
// it's not in cluster resource, permission denied
if attr.GetWorkspace() == "" && attr.GetNamespace() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
workspaceRole, err := o.am.GetWorkspaceRole(attr.GetWorkspace(), attr.GetUser().GetName())
workspaceRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.WorkspaceScope, attr.GetWorkspace(), attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check workspace role policy rules
if a, r, e := makeDecision(workspaceRole, attr); a == authorizer.DecisionAllow {
return a, r, e
if authorized, reason, err := o.makeDecision(workspaceRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, err
} else if err != nil {
return authorizer.DecisionDeny, "", err
}
// it's not in workspace resource, permission denied
if attr.GetNamespace() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
if attr.GetNamespace() != "" {
namespaceRole, err := o.am.GetNamespaceRole(attr.GetCluster(), attr.GetNamespace(), attr.GetUser().GetName())
if err != nil {
return authorizer.DecisionDeny, "", err
}
// check namespace role policy rules
if a, r, e := makeDecision(namespaceRole, attr); a == authorizer.DecisionAllow {
return a, r, e
namespaceRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.NamespaceScope, attr.GetNamespace(), attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check namespace role policy rules
if authorized, reason, err := o.makeDecision(namespaceRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, err
} else if err != nil {
return authorizer.DecisionDeny, "", err
}
return authorizer.DecisionDeny, "", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
// Make decision base on role
func makeDecision(role am.Role, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
func (o *opaAuthorizer) makeDecision(role *iamv1alpha2.Role, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
// Call the rego.New function to create an object that can be prepared or evaluated
// After constructing a new rego.Rego object you can call PrepareForEval() to obtain an executable query
query, err := rego.New(rego.Query("data.authz.allow"), rego.Module("authz.rego", role.GetRego())).PrepareForEval(context.Background())
for _, ruleRef := range role.Rules {
rule, err := o.am.GetPolicyRule(ruleRef.Name)
if err != nil {
if errors.IsNotFound(err) {
continue
}
return authorizer.DecisionDeny, "", err
}
// Call the rego.New function to create an object that can be prepared or evaluated
// After constructing a new rego.Rego object you can call PrepareForEval() to obtain an executable query
query, err := rego.New(rego.Query(defaultRegoQuery), rego.Module("authz.rego", rule.Rego)).PrepareForEval(context.Background())
if err != nil {
return authorizer.DecisionDeny, "", err
if err != nil {
klog.Errorf("rule syntax error:%s", err)
continue
}
// The policy decision is contained in the results returned by the Eval() call. You can inspect the decision and handle it accordingly.
results, err := query.Eval(context.Background(), rego.EvalInput(a))
if err != nil {
klog.Errorf("rule syntax error:%s", err)
continue
}
if len(results) > 0 && results[0].Expressions[0].Value == true {
return authorizer.DecisionAllow, "", nil
}
}
// data example
//{
// "User": {
// "Name": "admin",
// "UID": "0",
// "Groups": [
// "admin"
// ],
// "Extra": null
// },
// "Verb": "list",
// "Cluster": "cluster1",
// "Workspace": "",
// "Namespace": "",
// "APIGroup": "",
// "APIVersion": "v1",
// "Resource": "nodes",
// "Subresource": "",
// "Name": "",
// "KubernetesRequest": true,
// "ResourceRequest": true,
// "Path": "/api/v1/nodes"
//}
// The policy decision is contained in the results returned by the Eval() call. You can inspect the decision and handle it accordingly.
results, err := query.Eval(context.Background(), rego.EvalInput(a))
if err != nil {
return authorizer.DecisionDeny, "", err
}
if len(results) > 0 && results[0].Expressions[0].Value == true {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
func NewOPAAuthorizer(am am.AccessManagementInterface) *opaAuthorizer {

View File

@@ -19,22 +19,44 @@
package authorizerfactory
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/user"
iamvealpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"testing"
)
func TestPlatformRole(t *testing.T) {
platformRoles := map[string]am.FakeRole{"admin": {
Name: "admin",
Rego: "package authz\ndefault allow = true",
}, "anonymous": {
Name: "anonymous",
Rego: "package authz\ndefault allow = false",
}, "tom": {
Name: "tom",
Rego: `package authz
func prepare() (am.AccessManagementInterface, error) {
rules := []*iamvealpha2.PolicyRule{
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.PolicyRuleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "always-allow",
},
Rego: "package authz\ndefault allow = true",
}, {
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.PolicyRuleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "always-deny",
},
Rego: "package authz\ndefault allow = false",
}, {
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.PolicyRuleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "manage-cluster1-resources",
},
Rego: `package authz
default allow = false
allow {
resources_in_cluster1
@@ -42,11 +64,168 @@ allow {
resources_in_cluster1 {
input.Cluster == "cluster1"
}`,
},
},
}
operator := am.NewFakeAMOperator()
operator.Prepare(platformRoles, nil, nil, nil)
roles := []*iamvealpha2.Role{
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "global-admin",
},
Target: iamvealpha2.Target{
Scope: iamvealpha2.GlobalScope,
Name: "",
},
Rules: []iamvealpha2.RuleRef{
{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.PolicyRuleKind,
Name: "always-allow",
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "anonymous",
},
Target: iamvealpha2.Target{
Scope: iamvealpha2.GlobalScope,
Name: "",
},
Rules: []iamvealpha2.RuleRef{
{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.PolicyRuleKind,
Name: "always-deny",
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1-admin",
},
Target: iamvealpha2.Target{
Scope: iamvealpha2.GlobalScope,
Name: "",
},
Rules: []iamvealpha2.RuleRef{
{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.PolicyRuleKind,
Name: "manage-cluster1-resources",
},
},
},
}
roleBindings := []*iamvealpha2.RoleBinding{
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleBindingKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "global-admin",
},
Scope: iamvealpha2.GlobalScope,
RoleRef: iamvealpha2.RoleRef{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.RoleKind,
Name: "global-admin",
},
Subjects: []iamvealpha2.Subject{
{
Kind: iamvealpha2.UserKind,
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Name: "admin",
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleBindingKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "anonymous",
},
Scope: iamvealpha2.GlobalScope,
RoleRef: iamvealpha2.RoleRef{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.RoleKind,
Name: "anonymous",
},
Subjects: []iamvealpha2.Subject{
{
Kind: iamvealpha2.UserKind,
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Name: user.Anonymous,
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleBindingKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1-admin",
},
Scope: iamvealpha2.GlobalScope,
RoleRef: iamvealpha2.RoleRef{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.RoleKind,
Name: "cluster1-admin",
},
Subjects: []iamvealpha2.Subject{
{
Kind: iamvealpha2.UserKind,
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Name: "tom",
},
},
},
}
ksClient := fake.NewSimpleClientset()
informerFactory := externalversions.NewSharedInformerFactory(ksClient, 0)
for _, rule := range rules {
err := informerFactory.Iam().V1alpha2().PolicyRules().Informer().GetIndexer().Add(rule)
if err != nil {
return nil, fmt.Errorf("add rule:%s", err)
}
}
for _, role := range roles {
err := informerFactory.Iam().V1alpha2().Roles().Informer().GetIndexer().Add(role)
if err != nil {
return nil, fmt.Errorf("add role:%s", err)
}
}
for _, roleBinding := range roleBindings {
err := informerFactory.Iam().V1alpha2().RoleBindings().Informer().GetIndexer().Add(roleBinding)
if err != nil {
return nil, fmt.Errorf("add role binding:%s", err)
}
}
operator := am.NewAMOperator(ksClient, informerFactory)
return operator, nil
}
func TestGlobalRole(t *testing.T) {
operator, err := prepare()
if err != nil {
t.Fatal(err)
}
opa := NewOPAAuthorizer(operator)

View File

@@ -1,16 +1,18 @@
package query
type Field string
type Value string
const (
FieldName = "name"
FieldUID = "uid"
FieldCreationTimeStamp = "creationTimestamp"
FieldLastUpdateTimestamp = "lastUpdateTimestamp"
FieldLabel = "label"
FieldAnnotation = "annotation"
FieldNamespace = "namespace"
FieldStatus = "status"
FieldApplication = "application"
FieldOwner = "owner"
FieldOwnerReference = "ownerReference"
FieldOwnerKind = "ownerKind"
)
@@ -23,9 +25,11 @@ var SortableFields = []Field{
// Field contains all the query field that can be compared
var ComparableFields = []Field{
FieldName,
FieldUID,
FieldLabel,
FieldAnnotation,
FieldNamespace,
FieldStatus,
FieldApplication,
FieldOwner,
FieldOwnerReference,
FieldOwnerKind,
}

View File

@@ -3,7 +3,6 @@ package query
import (
"github.com/emicklei/go-restful"
"strconv"
"strings"
)
const (
@@ -16,24 +15,6 @@ const (
ParameterAscending = "ascending"
)
type Comparable interface {
Compare(Comparable) int
Contains(Comparable) bool
}
type ComparableString string
func (c ComparableString) Compare(comparable Comparable) int {
other := comparable.(ComparableString)
return strings.Compare(string(c), string(other))
}
func (c ComparableString) Contains(comparable Comparable) bool {
other := comparable.(ComparableString)
return strings.Contains(string(c), string(other))
}
// Query represents api search terms
type Query struct {
Pagination *Pagination
@@ -52,29 +33,30 @@ type Pagination struct {
// items per page
Limit int
// page number
Page int
// offset
Offset int
}
var NoPagination = newPagination(-1, -1)
var NoPagination = newPagination(-1, 0)
func newPagination(limit int, page int) *Pagination {
// make sure that pagination is valid
func newPagination(limit int, offset int) *Pagination {
return &Pagination{
Limit: limit,
Page: page,
Limit: limit,
Offset: offset,
}
}
func (p *Pagination) IsValidPagintaion() bool {
return p.Limit >= 0 && p.Page >= 0
}
func (p *Pagination) GetValidPagination(total int) (startIndex, endIndex int) {
if p.Limit == NoPagination.Limit {
return 0, total
}
func (p *Pagination) IsPageAvailable(total, startIndex int) bool {
return total > startIndex && p.Limit > 0
}
if p.Limit < 0 || p.Offset < 0 {
return 0, 0
}
func (p *Pagination) GetPaginationSettings(total int) (startIndex, endIndex int) {
startIndex = p.Limit * p.Page
startIndex = p.Limit * p.Offset
endIndex = startIndex + p.Limit
if endIndex > total {
@@ -86,33 +68,33 @@ func (p *Pagination) GetPaginationSettings(total int) (startIndex, endIndex int)
func New() *Query {
return &Query{
Pagination: &Pagination{
Limit: -1,
Page: -1,
},
SortBy: "",
Ascending: false,
Filters: []Filter{},
Pagination: NoPagination,
SortBy: "",
Ascending: false,
Filters: []Filter{},
}
}
type Filter struct {
Field Field
Value Comparable
Value Value
}
func ParseQueryParameter(request *restful.Request) *Query {
query := New()
limit, err := strconv.ParseInt(request.QueryParameter("limit"), 10, 0)
limit, err := strconv.Atoi(request.QueryParameter("limit"))
// equivalent to undefined, use the default value
if err != nil {
query.Pagination = NoPagination
limit = -1
}
page, err := strconv.Atoi(request.QueryParameter("page"))
// equivalent to undefined, use the default value
if err != nil {
page = 1
}
page, err := strconv.ParseInt(request.QueryParameter("page"), 10, 0)
if err == nil {
query.Pagination = newPagination(int(limit), int(page-1))
}
query.Pagination = newPagination(limit, page-1)
query.SortBy = Field(defaultString(request.QueryParameter("sortBy"), FieldCreationTimeStamp))
@@ -128,13 +110,12 @@ func ParseQueryParameter(request *restful.Request) *Query {
if len(f) != 0 {
query.Filters = append(query.Filters, Filter{
Field: field,
Value: ComparableString(f),
Value: Value(f),
})
}
}
return query
}
func defaultString(value, defaultValue string) string {

View File

@@ -16,7 +16,7 @@ func TestParseQueryParameter(t *testing.T) {
}{
{
"test normal case",
"name=foo&status=Running&application=book&page=1&limit=10&ascending=true",
"label=app.kubernetes.io/name:book&name=foo&status=Running&page=1&limit=10&ascending=true",
&Query{
Pagination: newPagination(10, 0),
SortBy: FieldCreationTimeStamp,
@@ -24,15 +24,15 @@ func TestParseQueryParameter(t *testing.T) {
Filters: []Filter{
{
FieldName,
ComparableString("foo"),
Value("foo"),
},
{
FieldLabel,
Value("app.kubernetes.io/name:book"),
},
{
FieldStatus,
ComparableString("Running"),
},
{
FieldApplication,
ComparableString("book"),
Value("Running"),
},
},
},
@@ -41,13 +41,10 @@ func TestParseQueryParameter(t *testing.T) {
"test bad case",
"xxxx=xxxx&dsfsw=xxxx&page=abc&limit=add&ascending=ssss",
&Query{
Pagination: &Pagination{
Limit: -1,
Page: -1,
},
SortBy: FieldCreationTimeStamp,
Ascending: false,
Filters: []Filter{},
Pagination: NoPagination,
SortBy: FieldCreationTimeStamp,
Ascending: false,
Filters: []Filter{},
},
},
}

View File

@@ -28,6 +28,18 @@ type FakeIamV1alpha2 struct {
*testing.Fake
}
func (c *FakeIamV1alpha2) PolicyRules() v1alpha2.PolicyRuleInterface {
return &FakePolicyRules{c}
}
func (c *FakeIamV1alpha2) Roles() v1alpha2.RoleInterface {
return &FakeRoles{c}
}
func (c *FakeIamV1alpha2) RoleBindings() v1alpha2.RoleBindingInterface {
return &FakeRoleBindings{c}
}
func (c *FakeIamV1alpha2) Users() v1alpha2.UserInterface {
return &FakeUsers{c}
}

View File

@@ -0,0 +1,120 @@
/*
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 client-gen. DO NOT EDIT.
package fake
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
)
// FakePolicyRules implements PolicyRuleInterface
type FakePolicyRules struct {
Fake *FakeIamV1alpha2
}
var policyrulesResource = schema.GroupVersionResource{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "policyrules"}
var policyrulesKind = schema.GroupVersionKind{Group: "iam.kubesphere.io", Version: "v1alpha2", Kind: "PolicyRule"}
// Get takes name of the policyRule, and returns the corresponding policyRule object, and an error if there is any.
func (c *FakePolicyRules) Get(name string, options v1.GetOptions) (result *v1alpha2.PolicyRule, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootGetAction(policyrulesResource, name), &v1alpha2.PolicyRule{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.PolicyRule), err
}
// List takes label and field selectors, and returns the list of PolicyRules that match those selectors.
func (c *FakePolicyRules) List(opts v1.ListOptions) (result *v1alpha2.PolicyRuleList, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootListAction(policyrulesResource, policyrulesKind, opts), &v1alpha2.PolicyRuleList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha2.PolicyRuleList{ListMeta: obj.(*v1alpha2.PolicyRuleList).ListMeta}
for _, item := range obj.(*v1alpha2.PolicyRuleList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested policyRules.
func (c *FakePolicyRules) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewRootWatchAction(policyrulesResource, opts))
}
// Create takes the representation of a policyRule and creates it. Returns the server's representation of the policyRule, and an error, if there is any.
func (c *FakePolicyRules) Create(policyRule *v1alpha2.PolicyRule) (result *v1alpha2.PolicyRule, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootCreateAction(policyrulesResource, policyRule), &v1alpha2.PolicyRule{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.PolicyRule), err
}
// Update takes the representation of a policyRule and updates it. Returns the server's representation of the policyRule, and an error, if there is any.
func (c *FakePolicyRules) Update(policyRule *v1alpha2.PolicyRule) (result *v1alpha2.PolicyRule, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootUpdateAction(policyrulesResource, policyRule), &v1alpha2.PolicyRule{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.PolicyRule), err
}
// Delete takes name of the policyRule and deletes it. Returns an error if one occurs.
func (c *FakePolicyRules) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewRootDeleteAction(policyrulesResource, name), &v1alpha2.PolicyRule{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakePolicyRules) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewRootDeleteCollectionAction(policyrulesResource, listOptions)
_, err := c.Fake.Invokes(action, &v1alpha2.PolicyRuleList{})
return err
}
// Patch applies the patch and returns the patched policyRule.
func (c *FakePolicyRules) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.PolicyRule, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootPatchSubresourceAction(policyrulesResource, name, pt, data, subresources...), &v1alpha2.PolicyRule{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.PolicyRule), err
}

View File

@@ -0,0 +1,120 @@
/*
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 client-gen. DO NOT EDIT.
package fake
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
)
// FakeRoles implements RoleInterface
type FakeRoles struct {
Fake *FakeIamV1alpha2
}
var rolesResource = schema.GroupVersionResource{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "roles"}
var rolesKind = schema.GroupVersionKind{Group: "iam.kubesphere.io", Version: "v1alpha2", Kind: "Role"}
// Get takes name of the role, and returns the corresponding role object, and an error if there is any.
func (c *FakeRoles) Get(name string, options v1.GetOptions) (result *v1alpha2.Role, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootGetAction(rolesResource, name), &v1alpha2.Role{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.Role), err
}
// List takes label and field selectors, and returns the list of Roles that match those selectors.
func (c *FakeRoles) List(opts v1.ListOptions) (result *v1alpha2.RoleList, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootListAction(rolesResource, rolesKind, opts), &v1alpha2.RoleList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha2.RoleList{ListMeta: obj.(*v1alpha2.RoleList).ListMeta}
for _, item := range obj.(*v1alpha2.RoleList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested roles.
func (c *FakeRoles) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewRootWatchAction(rolesResource, opts))
}
// Create takes the representation of a role and creates it. Returns the server's representation of the role, and an error, if there is any.
func (c *FakeRoles) Create(role *v1alpha2.Role) (result *v1alpha2.Role, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootCreateAction(rolesResource, role), &v1alpha2.Role{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.Role), err
}
// Update takes the representation of a role and updates it. Returns the server's representation of the role, and an error, if there is any.
func (c *FakeRoles) Update(role *v1alpha2.Role) (result *v1alpha2.Role, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootUpdateAction(rolesResource, role), &v1alpha2.Role{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.Role), err
}
// Delete takes name of the role and deletes it. Returns an error if one occurs.
func (c *FakeRoles) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewRootDeleteAction(rolesResource, name), &v1alpha2.Role{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeRoles) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewRootDeleteCollectionAction(rolesResource, listOptions)
_, err := c.Fake.Invokes(action, &v1alpha2.RoleList{})
return err
}
// Patch applies the patch and returns the patched role.
func (c *FakeRoles) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.Role, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootPatchSubresourceAction(rolesResource, name, pt, data, subresources...), &v1alpha2.Role{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.Role), err
}

View File

@@ -0,0 +1,120 @@
/*
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 client-gen. DO NOT EDIT.
package fake
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
)
// FakeRoleBindings implements RoleBindingInterface
type FakeRoleBindings struct {
Fake *FakeIamV1alpha2
}
var rolebindingsResource = schema.GroupVersionResource{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "rolebindings"}
var rolebindingsKind = schema.GroupVersionKind{Group: "iam.kubesphere.io", Version: "v1alpha2", Kind: "RoleBinding"}
// Get takes name of the roleBinding, and returns the corresponding roleBinding object, and an error if there is any.
func (c *FakeRoleBindings) Get(name string, options v1.GetOptions) (result *v1alpha2.RoleBinding, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootGetAction(rolebindingsResource, name), &v1alpha2.RoleBinding{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.RoleBinding), err
}
// List takes label and field selectors, and returns the list of RoleBindings that match those selectors.
func (c *FakeRoleBindings) List(opts v1.ListOptions) (result *v1alpha2.RoleBindingList, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootListAction(rolebindingsResource, rolebindingsKind, opts), &v1alpha2.RoleBindingList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha2.RoleBindingList{ListMeta: obj.(*v1alpha2.RoleBindingList).ListMeta}
for _, item := range obj.(*v1alpha2.RoleBindingList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested roleBindings.
func (c *FakeRoleBindings) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewRootWatchAction(rolebindingsResource, opts))
}
// Create takes the representation of a roleBinding and creates it. Returns the server's representation of the roleBinding, and an error, if there is any.
func (c *FakeRoleBindings) Create(roleBinding *v1alpha2.RoleBinding) (result *v1alpha2.RoleBinding, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootCreateAction(rolebindingsResource, roleBinding), &v1alpha2.RoleBinding{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.RoleBinding), err
}
// Update takes the representation of a roleBinding and updates it. Returns the server's representation of the roleBinding, and an error, if there is any.
func (c *FakeRoleBindings) Update(roleBinding *v1alpha2.RoleBinding) (result *v1alpha2.RoleBinding, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootUpdateAction(rolebindingsResource, roleBinding), &v1alpha2.RoleBinding{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.RoleBinding), err
}
// Delete takes name of the roleBinding and deletes it. Returns an error if one occurs.
func (c *FakeRoleBindings) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewRootDeleteAction(rolebindingsResource, name), &v1alpha2.RoleBinding{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeRoleBindings) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewRootDeleteCollectionAction(rolebindingsResource, listOptions)
_, err := c.Fake.Invokes(action, &v1alpha2.RoleBindingList{})
return err
}
// Patch applies the patch and returns the patched roleBinding.
func (c *FakeRoleBindings) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.RoleBinding, err error) {
obj, err := c.Fake.
Invokes(testing.NewRootPatchSubresourceAction(rolebindingsResource, name, pt, data, subresources...), &v1alpha2.RoleBinding{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha2.RoleBinding), err
}

View File

@@ -18,4 +18,10 @@ limitations under the License.
package v1alpha2
type PolicyRuleExpansion interface{}
type RoleExpansion interface{}
type RoleBindingExpansion interface{}
type UserExpansion interface{}

View File

@@ -26,6 +26,9 @@ import (
type IamV1alpha2Interface interface {
RESTClient() rest.Interface
PolicyRulesGetter
RolesGetter
RoleBindingsGetter
UsersGetter
}
@@ -34,6 +37,18 @@ type IamV1alpha2Client struct {
restClient rest.Interface
}
func (c *IamV1alpha2Client) PolicyRules() PolicyRuleInterface {
return newPolicyRules(c)
}
func (c *IamV1alpha2Client) Roles() RoleInterface {
return newRoles(c)
}
func (c *IamV1alpha2Client) RoleBindings() RoleBindingInterface {
return newRoleBindings(c)
}
func (c *IamV1alpha2Client) Users() UserInterface {
return newUsers(c)
}

View File

@@ -0,0 +1,164 @@
/*
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 client-gen. DO NOT EDIT.
package v1alpha2
import (
"time"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
scheme "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
)
// PolicyRulesGetter has a method to return a PolicyRuleInterface.
// A group's client should implement this interface.
type PolicyRulesGetter interface {
PolicyRules() PolicyRuleInterface
}
// PolicyRuleInterface has methods to work with PolicyRule resources.
type PolicyRuleInterface interface {
Create(*v1alpha2.PolicyRule) (*v1alpha2.PolicyRule, error)
Update(*v1alpha2.PolicyRule) (*v1alpha2.PolicyRule, error)
Delete(name string, options *v1.DeleteOptions) error
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
Get(name string, options v1.GetOptions) (*v1alpha2.PolicyRule, error)
List(opts v1.ListOptions) (*v1alpha2.PolicyRuleList, error)
Watch(opts v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.PolicyRule, err error)
PolicyRuleExpansion
}
// policyRules implements PolicyRuleInterface
type policyRules struct {
client rest.Interface
}
// newPolicyRules returns a PolicyRules
func newPolicyRules(c *IamV1alpha2Client) *policyRules {
return &policyRules{
client: c.RESTClient(),
}
}
// Get takes name of the policyRule, and returns the corresponding policyRule object, and an error if there is any.
func (c *policyRules) Get(name string, options v1.GetOptions) (result *v1alpha2.PolicyRule, err error) {
result = &v1alpha2.PolicyRule{}
err = c.client.Get().
Resource("policyrules").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of PolicyRules that match those selectors.
func (c *policyRules) List(opts v1.ListOptions) (result *v1alpha2.PolicyRuleList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha2.PolicyRuleList{}
err = c.client.Get().
Resource("policyrules").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested policyRules.
func (c *policyRules) Watch(opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Resource("policyrules").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch()
}
// Create takes the representation of a policyRule and creates it. Returns the server's representation of the policyRule, and an error, if there is any.
func (c *policyRules) Create(policyRule *v1alpha2.PolicyRule) (result *v1alpha2.PolicyRule, err error) {
result = &v1alpha2.PolicyRule{}
err = c.client.Post().
Resource("policyrules").
Body(policyRule).
Do().
Into(result)
return
}
// Update takes the representation of a policyRule and updates it. Returns the server's representation of the policyRule, and an error, if there is any.
func (c *policyRules) Update(policyRule *v1alpha2.PolicyRule) (result *v1alpha2.PolicyRule, err error) {
result = &v1alpha2.PolicyRule{}
err = c.client.Put().
Resource("policyrules").
Name(policyRule.Name).
Body(policyRule).
Do().
Into(result)
return
}
// Delete takes name of the policyRule and deletes it. Returns an error if one occurs.
func (c *policyRules) Delete(name string, options *v1.DeleteOptions) error {
return c.client.Delete().
Resource("policyrules").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *policyRules) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
var timeout time.Duration
if listOptions.TimeoutSeconds != nil {
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Resource("policyrules").
VersionedParams(&listOptions, scheme.ParameterCodec).
Timeout(timeout).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched policyRule.
func (c *policyRules) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.PolicyRule, err error) {
result = &v1alpha2.PolicyRule{}
err = c.client.Patch(pt).
Resource("policyrules").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View File

@@ -0,0 +1,164 @@
/*
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 client-gen. DO NOT EDIT.
package v1alpha2
import (
"time"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
scheme "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
)
// RolesGetter has a method to return a RoleInterface.
// A group's client should implement this interface.
type RolesGetter interface {
Roles() RoleInterface
}
// RoleInterface has methods to work with Role resources.
type RoleInterface interface {
Create(*v1alpha2.Role) (*v1alpha2.Role, error)
Update(*v1alpha2.Role) (*v1alpha2.Role, error)
Delete(name string, options *v1.DeleteOptions) error
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
Get(name string, options v1.GetOptions) (*v1alpha2.Role, error)
List(opts v1.ListOptions) (*v1alpha2.RoleList, error)
Watch(opts v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.Role, err error)
RoleExpansion
}
// roles implements RoleInterface
type roles struct {
client rest.Interface
}
// newRoles returns a Roles
func newRoles(c *IamV1alpha2Client) *roles {
return &roles{
client: c.RESTClient(),
}
}
// Get takes name of the role, and returns the corresponding role object, and an error if there is any.
func (c *roles) Get(name string, options v1.GetOptions) (result *v1alpha2.Role, err error) {
result = &v1alpha2.Role{}
err = c.client.Get().
Resource("roles").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of Roles that match those selectors.
func (c *roles) List(opts v1.ListOptions) (result *v1alpha2.RoleList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha2.RoleList{}
err = c.client.Get().
Resource("roles").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested roles.
func (c *roles) Watch(opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Resource("roles").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch()
}
// Create takes the representation of a role and creates it. Returns the server's representation of the role, and an error, if there is any.
func (c *roles) Create(role *v1alpha2.Role) (result *v1alpha2.Role, err error) {
result = &v1alpha2.Role{}
err = c.client.Post().
Resource("roles").
Body(role).
Do().
Into(result)
return
}
// Update takes the representation of a role and updates it. Returns the server's representation of the role, and an error, if there is any.
func (c *roles) Update(role *v1alpha2.Role) (result *v1alpha2.Role, err error) {
result = &v1alpha2.Role{}
err = c.client.Put().
Resource("roles").
Name(role.Name).
Body(role).
Do().
Into(result)
return
}
// Delete takes name of the role and deletes it. Returns an error if one occurs.
func (c *roles) Delete(name string, options *v1.DeleteOptions) error {
return c.client.Delete().
Resource("roles").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *roles) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
var timeout time.Duration
if listOptions.TimeoutSeconds != nil {
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Resource("roles").
VersionedParams(&listOptions, scheme.ParameterCodec).
Timeout(timeout).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched role.
func (c *roles) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.Role, err error) {
result = &v1alpha2.Role{}
err = c.client.Patch(pt).
Resource("roles").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View File

@@ -0,0 +1,164 @@
/*
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 client-gen. DO NOT EDIT.
package v1alpha2
import (
"time"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
scheme "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
)
// RoleBindingsGetter has a method to return a RoleBindingInterface.
// A group's client should implement this interface.
type RoleBindingsGetter interface {
RoleBindings() RoleBindingInterface
}
// RoleBindingInterface has methods to work with RoleBinding resources.
type RoleBindingInterface interface {
Create(*v1alpha2.RoleBinding) (*v1alpha2.RoleBinding, error)
Update(*v1alpha2.RoleBinding) (*v1alpha2.RoleBinding, error)
Delete(name string, options *v1.DeleteOptions) error
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
Get(name string, options v1.GetOptions) (*v1alpha2.RoleBinding, error)
List(opts v1.ListOptions) (*v1alpha2.RoleBindingList, error)
Watch(opts v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.RoleBinding, err error)
RoleBindingExpansion
}
// roleBindings implements RoleBindingInterface
type roleBindings struct {
client rest.Interface
}
// newRoleBindings returns a RoleBindings
func newRoleBindings(c *IamV1alpha2Client) *roleBindings {
return &roleBindings{
client: c.RESTClient(),
}
}
// Get takes name of the roleBinding, and returns the corresponding roleBinding object, and an error if there is any.
func (c *roleBindings) Get(name string, options v1.GetOptions) (result *v1alpha2.RoleBinding, err error) {
result = &v1alpha2.RoleBinding{}
err = c.client.Get().
Resource("rolebindings").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of RoleBindings that match those selectors.
func (c *roleBindings) List(opts v1.ListOptions) (result *v1alpha2.RoleBindingList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1alpha2.RoleBindingList{}
err = c.client.Get().
Resource("rolebindings").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested roleBindings.
func (c *roleBindings) Watch(opts v1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Resource("rolebindings").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch()
}
// Create takes the representation of a roleBinding and creates it. Returns the server's representation of the roleBinding, and an error, if there is any.
func (c *roleBindings) Create(roleBinding *v1alpha2.RoleBinding) (result *v1alpha2.RoleBinding, err error) {
result = &v1alpha2.RoleBinding{}
err = c.client.Post().
Resource("rolebindings").
Body(roleBinding).
Do().
Into(result)
return
}
// Update takes the representation of a roleBinding and updates it. Returns the server's representation of the roleBinding, and an error, if there is any.
func (c *roleBindings) Update(roleBinding *v1alpha2.RoleBinding) (result *v1alpha2.RoleBinding, err error) {
result = &v1alpha2.RoleBinding{}
err = c.client.Put().
Resource("rolebindings").
Name(roleBinding.Name).
Body(roleBinding).
Do().
Into(result)
return
}
// Delete takes name of the roleBinding and deletes it. Returns an error if one occurs.
func (c *roleBindings) Delete(name string, options *v1.DeleteOptions) error {
return c.client.Delete().
Resource("rolebindings").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *roleBindings) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
var timeout time.Duration
if listOptions.TimeoutSeconds != nil {
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Resource("rolebindings").
VersionedParams(&listOptions, scheme.ParameterCodec).
Timeout(timeout).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched roleBinding.
func (c *roleBindings) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.RoleBinding, err error) {
result = &v1alpha2.RoleBinding{}
err = c.client.Patch(pt).
Resource("rolebindings").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View File

@@ -74,6 +74,12 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Devops().V1alpha1().S2iRuns().Informer()}, nil
// Group=iam.kubesphere.io, Version=v1alpha2
case v1alpha2.SchemeGroupVersion.WithResource("policyrules"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Iam().V1alpha2().PolicyRules().Informer()}, nil
case v1alpha2.SchemeGroupVersion.WithResource("roles"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Iam().V1alpha2().Roles().Informer()}, nil
case v1alpha2.SchemeGroupVersion.WithResource("rolebindings"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Iam().V1alpha2().RoleBindings().Informer()}, nil
case v1alpha2.SchemeGroupVersion.WithResource("users"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Iam().V1alpha2().Users().Informer()}, nil

View File

@@ -24,6 +24,12 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// PolicyRules returns a PolicyRuleInformer.
PolicyRules() PolicyRuleInformer
// Roles returns a RoleInformer.
Roles() RoleInformer
// RoleBindings returns a RoleBindingInformer.
RoleBindings() RoleBindingInformer
// Users returns a UserInformer.
Users() UserInformer
}
@@ -39,6 +45,21 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// PolicyRules returns a PolicyRuleInformer.
func (v *version) PolicyRules() PolicyRuleInformer {
return &policyRuleInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
}
// Roles returns a RoleInformer.
func (v *version) Roles() RoleInformer {
return &roleInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
}
// RoleBindings returns a RoleBindingInformer.
func (v *version) RoleBindings() RoleBindingInformer {
return &roleBindingInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
}
// Users returns a UserInformer.
func (v *version) Users() UserInformer {
return &userInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}

View File

@@ -0,0 +1,88 @@
/*
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 informer-gen. DO NOT EDIT.
package v1alpha2
import (
time "time"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
versioned "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
internalinterfaces "kubesphere.io/kubesphere/pkg/client/informers/externalversions/internalinterfaces"
v1alpha2 "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
)
// PolicyRuleInformer provides access to a shared informer and lister for
// PolicyRules.
type PolicyRuleInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha2.PolicyRuleLister
}
type policyRuleInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// NewPolicyRuleInformer constructs a new informer for PolicyRule type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewPolicyRuleInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredPolicyRuleInformer(client, resyncPeriod, indexers, nil)
}
// NewFilteredPolicyRuleInformer constructs a new informer for PolicyRule type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredPolicyRuleInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IamV1alpha2().PolicyRules().List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IamV1alpha2().PolicyRules().Watch(options)
},
},
&iamv1alpha2.PolicyRule{},
resyncPeriod,
indexers,
)
}
func (f *policyRuleInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredPolicyRuleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *policyRuleInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&iamv1alpha2.PolicyRule{}, f.defaultInformer)
}
func (f *policyRuleInformer) Lister() v1alpha2.PolicyRuleLister {
return v1alpha2.NewPolicyRuleLister(f.Informer().GetIndexer())
}

View File

@@ -0,0 +1,88 @@
/*
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 informer-gen. DO NOT EDIT.
package v1alpha2
import (
time "time"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
versioned "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
internalinterfaces "kubesphere.io/kubesphere/pkg/client/informers/externalversions/internalinterfaces"
v1alpha2 "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
)
// RoleInformer provides access to a shared informer and lister for
// Roles.
type RoleInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha2.RoleLister
}
type roleInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// NewRoleInformer constructs a new informer for Role type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewRoleInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredRoleInformer(client, resyncPeriod, indexers, nil)
}
// NewFilteredRoleInformer constructs a new informer for Role type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredRoleInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IamV1alpha2().Roles().List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IamV1alpha2().Roles().Watch(options)
},
},
&iamv1alpha2.Role{},
resyncPeriod,
indexers,
)
}
func (f *roleInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredRoleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *roleInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&iamv1alpha2.Role{}, f.defaultInformer)
}
func (f *roleInformer) Lister() v1alpha2.RoleLister {
return v1alpha2.NewRoleLister(f.Informer().GetIndexer())
}

View File

@@ -0,0 +1,88 @@
/*
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 informer-gen. DO NOT EDIT.
package v1alpha2
import (
time "time"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
versioned "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
internalinterfaces "kubesphere.io/kubesphere/pkg/client/informers/externalversions/internalinterfaces"
v1alpha2 "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
)
// RoleBindingInformer provides access to a shared informer and lister for
// RoleBindings.
type RoleBindingInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha2.RoleBindingLister
}
type roleBindingInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// NewRoleBindingInformer constructs a new informer for RoleBinding type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewRoleBindingInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredRoleBindingInformer(client, resyncPeriod, indexers, nil)
}
// NewFilteredRoleBindingInformer constructs a new informer for RoleBinding type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredRoleBindingInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IamV1alpha2().RoleBindings().List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.IamV1alpha2().RoleBindings().Watch(options)
},
},
&iamv1alpha2.RoleBinding{},
resyncPeriod,
indexers,
)
}
func (f *roleBindingInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredRoleBindingInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *roleBindingInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&iamv1alpha2.RoleBinding{}, f.defaultInformer)
}
func (f *roleBindingInformer) Lister() v1alpha2.RoleBindingLister {
return v1alpha2.NewRoleBindingLister(f.Informer().GetIndexer())
}

View File

@@ -18,6 +18,18 @@ limitations under the License.
package v1alpha2
// PolicyRuleListerExpansion allows custom methods to be added to
// PolicyRuleLister.
type PolicyRuleListerExpansion interface{}
// RoleListerExpansion allows custom methods to be added to
// RoleLister.
type RoleListerExpansion interface{}
// RoleBindingListerExpansion allows custom methods to be added to
// RoleBindingLister.
type RoleBindingListerExpansion interface{}
// UserListerExpansion allows custom methods to be added to
// UserLister.
type UserListerExpansion interface{}

View File

@@ -0,0 +1,65 @@
/*
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 lister-gen. DO NOT EDIT.
package v1alpha2
import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
)
// PolicyRuleLister helps list PolicyRules.
type PolicyRuleLister interface {
// List lists all PolicyRules in the indexer.
List(selector labels.Selector) (ret []*v1alpha2.PolicyRule, err error)
// Get retrieves the PolicyRule from the index for a given name.
Get(name string) (*v1alpha2.PolicyRule, error)
PolicyRuleListerExpansion
}
// policyRuleLister implements the PolicyRuleLister interface.
type policyRuleLister struct {
indexer cache.Indexer
}
// NewPolicyRuleLister returns a new PolicyRuleLister.
func NewPolicyRuleLister(indexer cache.Indexer) PolicyRuleLister {
return &policyRuleLister{indexer: indexer}
}
// List lists all PolicyRules in the indexer.
func (s *policyRuleLister) List(selector labels.Selector) (ret []*v1alpha2.PolicyRule, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha2.PolicyRule))
})
return ret, err
}
// Get retrieves the PolicyRule from the index for a given name.
func (s *policyRuleLister) Get(name string) (*v1alpha2.PolicyRule, error) {
obj, exists, err := s.indexer.GetByKey(name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha2.Resource("policyrule"), name)
}
return obj.(*v1alpha2.PolicyRule), nil
}

View File

@@ -0,0 +1,65 @@
/*
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 lister-gen. DO NOT EDIT.
package v1alpha2
import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
)
// RoleLister helps list Roles.
type RoleLister interface {
// List lists all Roles in the indexer.
List(selector labels.Selector) (ret []*v1alpha2.Role, err error)
// Get retrieves the Role from the index for a given name.
Get(name string) (*v1alpha2.Role, error)
RoleListerExpansion
}
// roleLister implements the RoleLister interface.
type roleLister struct {
indexer cache.Indexer
}
// NewRoleLister returns a new RoleLister.
func NewRoleLister(indexer cache.Indexer) RoleLister {
return &roleLister{indexer: indexer}
}
// List lists all Roles in the indexer.
func (s *roleLister) List(selector labels.Selector) (ret []*v1alpha2.Role, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha2.Role))
})
return ret, err
}
// Get retrieves the Role from the index for a given name.
func (s *roleLister) Get(name string) (*v1alpha2.Role, error) {
obj, exists, err := s.indexer.GetByKey(name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha2.Resource("role"), name)
}
return obj.(*v1alpha2.Role), nil
}

View File

@@ -0,0 +1,65 @@
/*
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 lister-gen. DO NOT EDIT.
package v1alpha2
import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
)
// RoleBindingLister helps list RoleBindings.
type RoleBindingLister interface {
// List lists all RoleBindings in the indexer.
List(selector labels.Selector) (ret []*v1alpha2.RoleBinding, err error)
// Get retrieves the RoleBinding from the index for a given name.
Get(name string) (*v1alpha2.RoleBinding, error)
RoleBindingListerExpansion
}
// roleBindingLister implements the RoleBindingLister interface.
type roleBindingLister struct {
indexer cache.Indexer
}
// NewRoleBindingLister returns a new RoleBindingLister.
func NewRoleBindingLister(indexer cache.Indexer) RoleBindingLister {
return &roleBindingLister{indexer: indexer}
}
// List lists all RoleBindings in the indexer.
func (s *roleBindingLister) List(selector labels.Selector) (ret []*v1alpha2.RoleBinding, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha2.RoleBinding))
})
return ret, err
}
// Get retrieves the RoleBinding from the index for a given name.
func (s *roleBindingLister) Get(name string) (*v1alpha2.RoleBinding, error) {
obj, exists, err := s.indexer.GetByKey(name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha2.Resource("rolebinding"), name)
}
return obj.(*v1alpha2.RoleBinding), nil
}

View File

@@ -169,7 +169,6 @@ func (r *ReconcileClusterRoleBinding) updateRoleBindings(clusterRoleBinding *rba
if clusterRoleBinding.Name == getWorkspaceViewerRoleBindingName(workspaceName) {
found := &rbac.RoleBinding{}
viewerBinding := &rbac.RoleBinding{}
viewerBinding.Name = "viewer"
viewerBinding.Namespace = namespace.Name

View File

@@ -0,0 +1,111 @@
/*
*
* Copyright 2020 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 user
import (
"context"
"encoding/json"
"fmt"
"golang.org/x/crypto/bcrypt"
"kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"net/http"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"strconv"
)
const (
encryptedAnnotation = "iam.kubesphere.io/password-encrypted"
)
type EmailValidator struct {
Client client.Client
decoder *admission.Decoder
}
type PasswordCipher struct {
Client client.Client
decoder *admission.Decoder
}
func (a *EmailValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
user := &v1alpha2.User{}
err := a.decoder.Decode(req, user)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
email := user.Spec.Email
allUsers := v1alpha2.UserList{}
err = a.Client.List(ctx, &v1alpha2.UserList{}, &client.ListOptions{})
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
found := emailAlreadyExist(allUsers, email)
if !found {
return admission.Denied(fmt.Sprintf("email %s must be unique", email))
}
return admission.Allowed("")
}
func emailAlreadyExist(users v1alpha2.UserList, email string) bool {
for _, user := range users.Items {
if user.Spec.Email == email {
return true
}
}
return false
}
func (a *PasswordCipher) Handle(ctx context.Context, req admission.Request) admission.Response {
user := &v1alpha2.User{}
err := a.decoder.Decode(req, user)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
encrypted, err := strconv.ParseBool(user.Annotations[encryptedAnnotation])
if err != nil || !encrypted {
password, err := hashPassword(user.Spec.EncryptedPassword)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
user.Spec.EncryptedPassword = password
user.Annotations[encryptedAnnotation] = "true"
}
marshaledUser, err := json.Marshal(user)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
return admission.PatchResponseFromRaw(req.Object.Raw, marshaledUser)
}
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
return string(bytes), err
}

View File

@@ -2,24 +2,24 @@ package v1alpha2
import (
"github.com/emicklei/go-restful"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"kubesphere.io/kubesphere/pkg/api"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/iam/im"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
ldappool "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"strings"
)
type iamHandler struct {
amOperator am.AccessManagementInterface
imOperator im.IdentityManagementInterface
am am.AccessManagementInterface
im im.IdentityManagementInterface
}
func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *authoptions.AuthenticationOptions) *iamHandler {
func newIAMHandler(im im.IdentityManagementInterface, am am.AccessManagementInterface, options *authoptions.AuthenticationOptions) *iamHandler {
return &iamHandler{
amOperator: am.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
imOperator: im.NewLDAPOperator(ldapClient),
am: am,
im: im,
}
}
@@ -36,7 +36,22 @@ func (h *iamHandler) ModifyUser(request *restful.Request, response *restful.Resp
}
func (h *iamHandler) DescribeUser(req *restful.Request, resp *restful.Response) {
panic("implement me")
username := req.PathParameter("user")
user, err := h.im.DescribeUser(username)
if err != nil {
api.HandleInternalError(resp, req, err)
return
}
globalRole, err := h.am.GetRoleOfUserInTargetScope(iamv1alpha2.GlobalScope, "", username)
if err != nil {
api.HandleInternalError(resp, req, err)
return
}
result := iamv1alpha2.UserDetail{User: user, GlobalRole: globalRole}
resp.WriteEntity(result)
}
func (h *iamHandler) ListUsers(req *restful.Request, resp *restful.Response) {
@@ -48,9 +63,39 @@ func (h *iamHandler) ListUserRoles(req *restful.Request, resp *restful.Response)
}
func (h *iamHandler) ListRoles(req *restful.Request, resp *restful.Response) {
panic("implement me")
}
func (h *iamHandler) ListRolesOfUser(req *restful.Request, resp *restful.Response) {
username := req.PathParameter("user")
var roles []iamv1alpha2.Role
var err error
if strings.HasSuffix(req.Request.URL.Path, "workspaceroles") {
roles, err = h.am.ListRolesOfUser(iamv1alpha2.WorkspaceScope, username)
} else if strings.HasSuffix(req.Request.URL.Path, "clusterroles") {
roles, err = h.am.ListRolesOfUser(iamv1alpha2.ClusterScope, username)
} else if strings.HasSuffix(req.Request.URL.Path, "namespaceroles") {
roles, err = h.am.ListRolesOfUser(iamv1alpha2.NamespaceScope, username)
}
if err != nil {
api.HandleInternalError(resp, req, err)
return
}
result := iamv1alpha2.RoleList{
TypeMeta: v1.TypeMeta{
Kind: "List",
APIVersion: "v1",
},
ListMeta: v1.ListMeta{},
Items: roles,
}
resp.WriteEntity(result)
}
func (h *iamHandler) ListClusterRoles(req *restful.Request, resp *restful.Response) {
panic("implement me")
}

View File

@@ -22,15 +22,14 @@ import (
"github.com/emicklei/go-restful-openapi"
"k8s.io/apimachinery/pkg/runtime/schema"
"kubesphere.io/kubesphere/pkg/api"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/iam/im"
"kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
ldappool "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"net/http"
)
@@ -38,19 +37,38 @@ const groupName = "iam.kubesphere.io"
var GroupVersion = schema.GroupVersion{Group: groupName, Version: "v1alpha2"}
func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *authoptions.AuthenticationOptions) error {
func AddToContainer(container *restful.Container, im im.IdentityManagementInterface, am am.AccessManagementInterface, options *authoptions.AuthenticationOptions) error {
ws := runtime.NewWebService(GroupVersion)
handler := newIAMHandler(k8sClient, factory, ldapClient, cacheClient, options)
handler := newIAMHandler(im, am, options)
// implemented by create CRD object.
//ws.Route(ws.POST("/users"))
//ws.Route(ws.DELETE("/users/{user}"))
//ws.Route(ws.PUT("/users/{user}"))
//ws.Route(ws.GET("/users/{user}"))
ws.Route(ws.GET("/users/{user}").
To(handler.DescribeUser).
Doc("Retrieve user details.").
Param(ws.PathParameter("user", "username")).
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.UserDetail{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
// TODO move to resources api
//ws.Route(ws.GET("/users"))
ws.Route(ws.GET("/users/{user}/workspaceroles").
To(handler.ListRolesOfUser).
Doc("Retrieve user roles in workspaces.").
Param(ws.PathParameter("user", "username")).
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.RoleList{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
ws.Route(ws.GET("/users/{user}/clusterroles").
To(handler.ListRolesOfUser).
Doc("Retrieve user roles in clusters.").
Param(ws.PathParameter("user", "username")).
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.RoleList{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
ws.Route(ws.GET("/users/{user}/namespaceroles").
To(handler.ListRolesOfUser).
Doc("Retrieve user roles in namespaces.").
Param(ws.PathParameter("user", "username")).
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.RoleList{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
ws.Route(ws.GET("/namespaces/{namespace}/roles").
To(handler.ListRoles).
@@ -104,6 +122,6 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer
Returns(http.StatusOK, api.StatusOK, errors.Error{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
c.Add(ws)
container.Add(ws)
return nil
}

View File

@@ -49,6 +49,7 @@ func AddToContainer(c *restful.Container, client kubernetes.Interface, factory i
webservice.Route(webservice.GET("/namespaces/{namespace}/{resources}").
To(handler.handleListNamespaceResources).
Deprecate().
Metadata(restfulspec.KeyOpenAPITags, []string{constants.NamespaceResourcesTag}).
Doc("Namespace level resource query").
Param(webservice.PathParameter("namespace", "the name of the project")).
@@ -66,9 +67,10 @@ func AddToContainer(c *restful.Container, client kubernetes.Interface, factory i
webservice.Route(webservice.GET("/{resources}").
To(handler.handleListNamespaceResources).
Deprecate().
Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.ClusterResourcesTag}).
Doc("Cluster level resource query").
Doc("Cluster level resources").
Param(webservice.PathParameter("resources", "cluster level resource type, e.g. nodes,workspaces,storageclasses,clusterroles.")).
Param(webservice.QueryParameter(params.ConditionsParam, "query conditions, connect multiple conditions with commas, equal symbol for exact query, wave symbol for fuzzy query e.g. name~a").
Required(false).

View File

@@ -11,7 +11,7 @@ import (
)
type Handler struct {
namespacedResourceGetter *resource.NamespacedResourceGetter
namespacedResourceGetter *resource.ResourceGetter
componentsGetter components.ComponentsGetter
}
@@ -22,22 +22,8 @@ func New(factory informers.InformerFactory) *Handler {
}
}
func (h Handler) handleGetNamespacedResource(request *restful.Request, response *restful.Response) {
resource := request.PathParameter("resources")
namespace := request.PathParameter("namespace")
name := request.PathParameter("name")
result, err := h.namespacedResourceGetter.Get(resource, namespace, name)
if err != nil {
api.HandleInternalError(response, nil, err)
return
}
response.WriteHeaderAndEntity(http.StatusOK, result)
}
// handleListNamedResource retrieves namespaced scope resources
func (h Handler) handleListNamespacedResource(request *restful.Request, response *restful.Response) {
// handleListResources retrieves resources
func (h Handler) handleListResources(request *restful.Request, response *restful.Response) {
query := query.ParseQueryParameter(request)
resource := request.PathParameter("resources")
namespace := request.PathParameter("namespace")
@@ -48,7 +34,7 @@ func (h Handler) handleListNamespacedResource(request *restful.Request, response
return
}
response.WriteHeaderAndEntity(http.StatusOK, result)
response.WriteEntity(result)
}
func (h Handler) handleGetComponentStatus(request *restful.Request, response *restful.Response) {

View File

@@ -45,14 +45,26 @@ func AddToContainer(c *restful.Container, informerFactory informers.InformerFact
webservice := runtime.NewWebService(GroupVersion)
handler := New(informerFactory)
webservice.Route(webservice.GET("/{resources}").
To(handler.handleListResources).
Metadata(restfulspec.KeyOpenAPITags, []string{tagNamespacedResource}).
Doc("Cluster level resources").
Param(webservice.PathParameter("resources", "cluster level resource type, e.g. pods,jobs,configmaps,services.")).
Param(webservice.QueryParameter(query.ParameterName, "name used to do filtering").Required(false)).
Param(webservice.QueryParameter(query.ParameterPage, "page").Required(false).DataFormat("page=%d").DefaultValue("page=1")).
Param(webservice.QueryParameter(query.ParameterLimit, "limit").Required(false)).
Param(webservice.QueryParameter(query.ParameterAscending, "sort parameters, e.g. reverse=true").Required(false).DefaultValue("ascending=false")).
Param(webservice.QueryParameter(query.ParameterOrderBy, "sort parameters, e.g. orderBy=createTime")).
Returns(http.StatusOK, ok, api.ListResult{}))
webservice.Route(webservice.GET("/namespaces/{namespace}/{resources}").
To(handler.handleGetNamespacedResource).
To(handler.handleListResources).
Metadata(restfulspec.KeyOpenAPITags, []string{tagNamespacedResource}).
Doc("Namespace level resource query").
Param(webservice.PathParameter("namespace", "the name of the project")).
Param(webservice.PathParameter("resources", "namespace level resource type, e.g. pods,jobs,configmaps,services.")).
Param(webservice.QueryParameter(query.ParameterName, "name used to do filtering").Required(false)).
Param(webservice.QueryParameter(query.ParameterPage, "page").Required(false).DataFormat("page=%d").DefaultValue("page=0")).
Param(webservice.QueryParameter(query.ParameterPage, "page").Required(false).DataFormat("page=%d").DefaultValue("page=1")).
Param(webservice.QueryParameter(query.ParameterLimit, "limit").Required(false)).
Param(webservice.QueryParameter(query.ParameterAscending, "sort parameters, e.g. reverse=true").Required(false).DefaultValue("ascending=false")).
Param(webservice.QueryParameter(query.ParameterOrderBy, "sort parameters, e.g. orderBy=createTime")).

View File

@@ -1,196 +1,38 @@
package v1alpha2
import (
"errors"
"github.com/emicklei/go-restful"
v1 "k8s.io/api/core/v1"
k8serr "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/monitoring"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/tenant"
apierr "kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"net/http"
)
type tenantHandler struct {
tenant tenant.Interface
am am.AccessManagementInterface
}
func newTenantHandler(k8sClient k8s.Client, factory informers.InformerFactory, db *mysql.Database) *tenantHandler {
func newTenantHandler(k8sClient k8s.Client, factory informers.InformerFactory) *tenantHandler {
return &tenantHandler{
tenant: tenant.New(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory(), factory.KubeSphereSharedInformerFactory(), db),
am: am.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
tenant: tenant.New(k8sClient, factory),
}
}
func (h *tenantHandler) ListWorkspaces(req *restful.Request, resp *restful.Response) {
username := req.HeaderParameter(constants.UserNameHeader)
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
limit, offset := params.ParsePaging(req)
reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true)
conditions, err := params.ParseConditions(req)
user, ok := request.UserFrom(req.Request.Context())
if err != nil {
if !ok {
err := errors.New("cannot obtain user info")
klog.Errorln(err)
api.HandleBadRequest(resp, nil, err)
api.HandleForbidden(resp, nil, err)
return
}
result, err := h.tenant.ListWorkspaces(username, conditions, orderBy, reverse, limit, offset)
if err != nil {
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteAsJson(result)
}
func (h *tenantHandler) DescribeWorkspace(req *restful.Request, resp *restful.Response) {
username := req.HeaderParameter(constants.UserNameHeader)
workspaceName := req.PathParameter("workspace")
result, err := h.tenant.DescribeWorkspace(username, workspaceName)
if err != nil {
if k8serr.IsNotFound(err) {
api.HandleNotFound(resp, nil, err)
} else {
api.HandleInternalError(resp, nil, err)
}
return
}
resp.WriteAsJson(result)
}
func (h *tenantHandler) ListNamespaces(req *restful.Request, resp *restful.Response) {
workspace := req.PathParameter("workspace")
username := req.PathParameter("member")
// /workspaces/{workspace}/members/{username}/namespaces
if username == "" {
// /workspaces/{workspace}/namespaces
username = req.HeaderParameter(constants.UserNameHeader)
}
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
limit, offset := params.ParsePaging(req)
reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true)
conditions, err := params.ParseConditions(req)
if err != nil {
api.HandleBadRequest(resp, nil, err)
return
}
conditions.Match[constants.WorkspaceLabelKey] = workspace
result, err := h.tenant.ListNamespaces(username, conditions, orderBy, reverse, limit, offset)
if err != nil {
api.HandleInternalError(resp, nil, err)
return
}
namespaces := make([]*v1.Namespace, 0)
for _, item := range result.Items {
namespaces = append(namespaces, item.(*v1.Namespace).DeepCopy())
}
namespaces = monitoring.GetNamespacesWithMetrics(namespaces)
items := make([]interface{}, 0)
for _, item := range namespaces {
items = append(items, item)
}
result.Items = items
resp.WriteAsJson(result)
}
func (h *tenantHandler) CreateNamespace(req *restful.Request, resp *restful.Response) {
workspace := req.PathParameter("workspace")
username := req.HeaderParameter(constants.UserNameHeader)
var namespace v1.Namespace
err := req.ReadEntity(&namespace)
if err != nil {
api.HandleNotFound(resp, nil, err)
return
}
_, err = h.tenant.DescribeWorkspace("", workspace)
if err != nil {
if k8serr.IsNotFound(err) {
api.HandleForbidden(resp, nil, err)
} else {
api.HandleInternalError(resp, nil, err)
}
return
}
created, err := h.tenant.CreateNamespace(workspace, &namespace, username)
if err != nil {
if k8serr.IsAlreadyExists(err) {
resp.WriteHeaderAndEntity(http.StatusConflict, err)
} else {
api.HandleInternalError(resp, nil, err)
}
return
}
resp.WriteAsJson(created)
}
func (h *tenantHandler) DeleteNamespace(req *restful.Request, resp *restful.Response) {
workspace := req.PathParameter("workspace")
namespace := req.PathParameter("namespace")
err := h.tenant.DeleteNamespace(workspace, namespace)
if err != nil {
if k8serr.IsNotFound(err) {
api.HandleNotFound(resp, nil, err)
} else {
api.HandleInternalError(resp, nil, err)
}
return
}
resp.WriteAsJson(apierr.None)
}
func (h *tenantHandler) ListDevopsProjects(req *restful.Request, resp *restful.Response) {
workspace := req.PathParameter("workspace")
username := req.PathParameter("member")
if username == "" {
username = req.HeaderParameter(constants.UserNameHeader)
}
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
limit, offset := params.ParsePaging(req)
reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true)
conditions, err := params.ParseConditions(req)
if err != nil {
api.HandleBadRequest(resp, nil, err)
return
}
conditions.Match["workspace"] = workspace
result, err := h.tenant.ListDevopsProjects(username, conditions, orderBy, reverse, limit, offset)
result, err := h.tenant.ListWorkspaces(user.GetName())
if err != nil {
api.HandleInternalError(resp, nil, err)
@@ -200,40 +42,24 @@ func (h *tenantHandler) ListDevopsProjects(req *restful.Request, resp *restful.R
resp.WriteEntity(result)
}
func (h *tenantHandler) GetDevOpsProjectsCount(req *restful.Request, resp *restful.Response) {
username := req.HeaderParameter(constants.UserNameHeader)
func (h *tenantHandler) ListNamespaces(req *restful.Request, resp *restful.Response) {
user, ok := request.UserFrom(req.Request.Context())
result, err := h.tenant.ListDevopsProjects(username, nil, "", false, 1, 0)
if err != nil {
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(struct {
Count int `json:"count"`
}{Count: result.TotalCount})
}
func (h *tenantHandler) DeleteDevopsProject(req *restful.Request, resp *restful.Response) {
projectId := req.PathParameter("devops")
workspace := req.PathParameter("workspace")
username := req.HeaderParameter(constants.UserNameHeader)
_, err := h.tenant.DescribeWorkspace("", workspace)
if err != nil {
api.HandleInternalError(resp, req, err)
if !ok {
err := errors.New("cannot obtain user info")
klog.Errorln(err)
api.HandleForbidden(resp, nil, err)
return
}
err = h.tenant.DeleteDevOpsProject(username, projectId)
worksapceName := req.PathParameter("workspace")
result, err := h.tenant.ListNamespaces(worksapceName, user.GetName())
if err != nil {
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(apierr.None)
}
func (h *tenantHandler) CreateDevopsProject(req *restful.Request, resp *restful.Response) {
resp.WriteEntity(result)
}

View File

@@ -23,17 +23,11 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"kubesphere.io/kubesphere/pkg/api"
devopsv1alpha2 "kubesphere.io/kubesphere/pkg/api/devops/v1alpha2"
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"net/http"
)
@@ -43,95 +37,21 @@ const (
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"}
func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory, db *mysql.Database) error {
func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory) error {
ws := runtime.NewWebService(GroupVersion)
handler := newTenantHandler(k8sClient, factory, db)
handler := newTenantHandler(k8sClient, factory)
ws.Route(ws.GET("/workspaces").
To(handler.ListWorkspaces).
Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}).
Doc("List all workspaces that belongs to the current user").
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}").
To(handler.DescribeWorkspace).
Doc("Describe the specified workspace").
Param(ws.PathParameter("workspace", "workspace name")).
Returns(http.StatusOK, api.StatusOK, v1alpha1.Workspace{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}/namespaces").
To(handler.ListNamespaces).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("List the namespaces of the specified workspace for the current user").
Returns(http.StatusOK, api.StatusOK, []v1.Namespace{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}/members/{member}/namespaces").
To(handler.ListNamespaces).
Param(ws.PathParameter("workspace", "workspace name")).
Param(ws.PathParameter("member", "workspace member's username")).
Doc("List the namespaces for the workspace member").
Returns(http.StatusOK, api.StatusOK, []v1.Namespace{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.POST("/workspaces/{workspace}/namespaces").
To(handler.CreateNamespace).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("Create a namespace in the specified workspace").
Returns(http.StatusOK, api.StatusOK, []v1.Namespace{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.DELETE("/workspaces/{workspace}/namespaces/{namespace}").
To(handler.DeleteNamespace).
Param(ws.PathParameter("workspace", "workspace name")).
Param(ws.PathParameter("namespace", "the name of the namespace")).
Doc("Delete the specified namespace from the workspace").
Returns(http.StatusOK, api.StatusOK, errors.Error{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}/devops").
To(handler.ListDevopsProjects).
Param(ws.PathParameter("workspace", "workspace name")).
Param(ws.QueryParameter(params.PagingParam, "page").
Required(false).
DataFormat("limit=%d,page=%d").
DefaultValue("limit=10,page=1")).
Param(ws.QueryParameter(params.ConditionsParam, "query conditions").
Required(false).
DataFormat("key=%s,key~%s")).
Doc("List devops projects for the current user").
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}/members/{member}/devops").
To(handler.ListDevopsProjects).
Param(ws.PathParameter("workspace", "workspace name")).
Param(ws.PathParameter("member", "workspace member's username")).
Param(ws.QueryParameter(params.PagingParam, "page").
Required(false).
DataFormat("limit=%d,page=%d").
DefaultValue("limit=10,page=1")).
Param(ws.QueryParameter(params.ConditionsParam, "query conditions").
Required(false).
DataFormat("key=%s,key~%s")).
Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}).
Doc("List the devops projects for the workspace member").
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/devopscount").
To(handler.GetDevOpsProjectsCount).
Returns(http.StatusOK, api.StatusOK, struct {
Count uint32 `json:"count"`
}{}).
Doc("Get the devops projects count for the member").
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.POST("/workspaces/{workspace}/devops").
To(handler.CreateDevopsProject).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("Create a devops project in the specified workspace").
Reads(devopsv1alpha2.DevOpsProject{}).
Returns(http.StatusOK, api.StatusOK, devopsv1alpha2.DevOpsProject{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.DELETE("/workspaces/{workspace}/devops/{devops}").
To(handler.DeleteDevopsProject).
Param(ws.PathParameter("workspace", "workspace name")).
Param(ws.PathParameter("devops", "devops project ID")).
Doc("Delete the specified devops project from the workspace").
Returns(http.StatusOK, api.StatusOK, devopsv1alpha2.DevOpsProject{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
c.Add(ws)
return nil

View File

@@ -18,61 +18,118 @@
package am
import (
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/clusterrole"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/role"
)
const (
ClusterRoleKind = "ClusterRole"
NamespaceAdminRoleBindName = "admin"
NamespaceViewerRoleBindName = "viewer"
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"net/http"
)
type AccessManagementInterface interface {
GetPlatformRole(username string) (Role, error)
GetClusterRole(cluster, username string) (Role, error)
GetWorkspaceRole(workspace, username string) (Role, error)
GetNamespaceRole(cluster, namespace, username string) (Role, error)
}
type Role interface {
GetName() string
GetRego() string
ListRolesOfUser(scope iamv1alpha2.Scope, username string) ([]iamv1alpha2.Role, error)
GetRoleOfUserInTargetScope(scope iamv1alpha2.Scope, target string, username string) (*iamv1alpha2.Role, error)
GetPolicyRule(name string) (*iamv1alpha2.PolicyRule, error)
}
type amOperator struct {
informers informers.SharedInformerFactory
resources resource.ResourceGetter
kubeClient kubernetes.Interface
informers informers.SharedInformerFactory
ksClient kubesphere.Interface
}
func NewAMOperator(kubeClient kubernetes.Interface, informers informers.SharedInformerFactory) AccessManagementInterface {
resourceGetter := resource.ResourceGetter{}
resourceGetter.Add(v1alpha2.Role, role.NewRoleSearcher(informers))
resourceGetter.Add(v1alpha2.ClusterRoles, clusterrole.NewClusterRoleSearcher(informers))
func NewAMOperator(ksClient kubesphere.Interface, informers informers.SharedInformerFactory) AccessManagementInterface {
return &amOperator{
informers: informers,
resources: resourceGetter,
kubeClient: kubeClient,
informers: informers,
ksClient: ksClient,
}
}
func (am *amOperator) GetPlatformRole(username string) (Role, error) {
panic("implement me")
func containsUser(subjets []iamv1alpha2.Subject, username string) bool {
for _, sub := range subjets {
if sub.Kind == iamv1alpha2.UserKind && sub.Name == username {
return true
}
}
return false
}
func (am *amOperator) GetClusterRole(cluster, username string) (Role, error) {
panic("implement me")
func (am *amOperator) ListRolesOfUser(scope iamv1alpha2.Scope, username string) ([]iamv1alpha2.Role, error) {
lister := am.informers.Iam().V1alpha2().RoleBindings().Lister()
roleBindings, err := lister.List(labels.Everything())
if err != nil {
klog.Error(err)
return nil, err
}
roleBindingsInScope := filterRoleBindingByScope(roleBindings, scope)
roles := make([]iamv1alpha2.Role, 0)
for _, roleBinding := range roleBindingsInScope {
if containsUser(roleBinding.Subjects, username) {
role, err := am.informers.
Iam().V1alpha2().Roles().Lister().Get(roleBinding.RoleRef.Name)
if err != nil {
if errors.IsNotFound(err) {
continue
}
return nil, err
}
roles = append(roles, *role)
}
}
return roles, nil
}
func (am *amOperator) GetWorkspaceRole(workspace, username string) (Role, error) {
panic("implement me")
func filterRoleBindingByScope(roles []*iamv1alpha2.RoleBinding, scope iamv1alpha2.Scope) []*iamv1alpha2.RoleBinding {
result := make([]*iamv1alpha2.RoleBinding, 0)
for _, role := range roles {
if role.Scope == scope {
result = append(result, role)
}
}
return result
}
func (am *amOperator) GetNamespaceRole(cluster, namespace, username string) (Role, error) {
panic("implement me")
func (am *amOperator) GetPolicyRule(name string) (*iamv1alpha2.PolicyRule, error) {
lister := am.informers.Iam().V1alpha2().PolicyRules().Lister()
return lister.Get(name)
}
// Users can only bind one role at each level
func (am *amOperator) GetRoleOfUserInTargetScope(scope iamv1alpha2.Scope, target, username string) (*iamv1alpha2.Role, error) {
roles, err := am.ListRolesOfUser(scope, username)
if err != nil {
klog.Error(err)
return nil, err
}
for _, role := range roles {
if role.Target.Name == iamv1alpha2.TargetAll ||
role.Target.Name == target {
return &role, nil
}
}
err = &errors.StatusError{ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusNotFound,
Reason: metav1.StatusReasonNotFound,
Details: &metav1.StatusDetails{
Group: iamv1alpha2.SchemeGroupVersion.Group,
Kind: iamv1alpha2.RoleBindingKind,
},
Message: fmt.Sprintf("role bind not found in %s %s scope", target, scope),
}}
klog.Error(err)
return nil, err
}

View File

@@ -1,141 +0,0 @@
/*
*
* Copyright 2020 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 am
import (
"encoding/json"
"fmt"
"k8s.io/apiserver/pkg/authentication/user"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
)
type FakeRole struct {
Name string
Rego string
}
type FakeOperator struct {
cache cache.Interface
}
func (f FakeOperator) queryFakeRole(cacheKey string) (Role, error) {
data, err := f.cache.Get(cacheKey)
if err != nil {
if err == cache.ErrNoSuchKey {
return &FakeRole{
Name: "DenyAll",
Rego: "package authz\ndefault allow = false",
}, nil
}
return nil, err
}
var role FakeRole
err = json.Unmarshal([]byte(data), &role)
if err != nil {
return nil, err
}
return role, nil
}
func (f FakeOperator) saveFakeRole(cacheKey string, role FakeRole) error {
data, err := json.Marshal(role)
if err != nil {
return err
}
return f.cache.Set(cacheKey, string(data), 0)
}
func (f FakeOperator) GetPlatformRole(username string) (Role, error) {
return f.queryFakeRole(platformRoleCacheKey(username))
}
func (f FakeOperator) GetClusterRole(cluster, username string) (Role, error) {
return f.queryFakeRole(clusterRoleCacheKey(cluster, username))
}
func (f FakeOperator) GetWorkspaceRole(workspace, username string) (Role, error) {
return f.queryFakeRole(workspaceRoleCacheKey(workspace, username))
}
func (f FakeOperator) GetNamespaceRole(cluster, namespace, username string) (Role, error) {
return f.queryFakeRole(namespaceRoleCacheKey(cluster, namespace, username))
}
func (f FakeOperator) Prepare(platformRoles map[string]FakeRole, clusterRoles map[string]map[string]FakeRole, workspaceRoles map[string]map[string]FakeRole, namespaceRoles map[string]map[string]map[string]FakeRole) {
for username, role := range platformRoles {
f.saveFakeRole(platformRoleCacheKey(username), role)
}
for cluster, roles := range clusterRoles {
for username, role := range roles {
f.saveFakeRole(clusterRoleCacheKey(cluster, username), role)
}
}
for workspace, roles := range workspaceRoles {
for username, role := range roles {
f.saveFakeRole(workspaceRoleCacheKey(workspace, username), role)
}
}
for cluster, nsRoles := range namespaceRoles {
for namespace, roles := range nsRoles {
for username, role := range roles {
f.saveFakeRole(namespaceRoleCacheKey(cluster, namespace, username), role)
}
}
}
}
func namespaceRoleCacheKey(cluster, namespace, username string) string {
return fmt.Sprintf("cluster.%s.namespaces.%s.roles.%s", cluster, namespace, username)
}
func clusterRoleCacheKey(cluster, username string) string {
return fmt.Sprintf("cluster.%s.roles.%s", cluster, username)
}
func workspaceRoleCacheKey(workspace, username string) string {
return fmt.Sprintf("workspace.%s.roles.%s", workspace, username)
}
func platformRoleCacheKey(username string) string {
return fmt.Sprintf("platform.roles.%s", username)
}
func (f FakeRole) GetName() string {
return f.Name
}
func (f FakeRole) GetRego() string {
return f.Rego
}
func NewFakeAMOperator() *FakeOperator {
operator := &FakeOperator{cache: cache.NewSimpleCache()}
operator.saveFakeRole(platformRoleCacheKey("admin"), FakeRole{
Name: "admin",
Rego: "package authz\ndefault allow = true",
})
operator.saveFakeRole(platformRoleCacheKey(user.Anonymous), FakeRole{
Name: "admin",
Rego: `package authz
default allow = false
`,
})
return operator
}

View File

@@ -19,76 +19,20 @@ package im
import (
"github.com/pkg/errors"
"kubesphere.io/kubesphere/pkg/api/iam"
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
)
type IdentityManagementInterface interface {
CreateUser(user *iam.User) (*iam.User, error)
CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error)
DeleteUser(username string) error
ModifyUser(user *iam.User) (*iam.User, error)
DescribeUser(username string) (*iam.User, error)
Authenticate(username, password string) (*iam.User, error)
}
type imOperator struct {
ldapClient ldap.Interface
ModifyUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error)
DescribeUser(username string) (*iamv1alpha2.User, error)
Authenticate(username, password string) (*iamv1alpha2.User, error)
}
var (
AuthRateLimitExceeded = errors.New("user auth rate limit exceeded")
UserAlreadyExists = errors.New("user already exists")
UserNotExists = errors.New("user not exists")
AuthRateLimitExceeded = errors.New("user auth rate limit exceeded")
AuthFailedIncorrectPassword = errors.New("incorrect password")
UserAlreadyExists = errors.New("user already exists")
UserNotExists = errors.New("user not exists")
)
func NewLDAPOperator(ldapClient ldap.Interface) IdentityManagementInterface {
return &imOperator{
ldapClient: ldapClient,
}
}
func (im *imOperator) ModifyUser(user *iam.User) (*iam.User, error) {
err := im.ldapClient.Update(user)
if err != nil {
return nil, err
}
return im.ldapClient.Get(user.Name)
}
func (im *imOperator) Authenticate(username, password string) (*iam.User, error) {
user, err := im.ldapClient.Get(username)
if err != nil {
return nil, err
}
err = im.ldapClient.Authenticate(user.Name, password)
if err != nil {
return nil, err
}
return user, nil
}
func (im *imOperator) DescribeUser(username string) (*iam.User, error) {
return im.ldapClient.Get(username)
}
func (im *imOperator) DeleteUser(username string) error {
return im.ldapClient.Delete(username)
}
func (im *imOperator) CreateUser(user *iam.User) (*iam.User, error) {
err := im.ldapClient.Create(user)
if err != nil {
return nil, err
}
return user, nil
}

View File

@@ -0,0 +1,85 @@
/*
*
* Copyright 2020 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 im
import (
"golang.org/x/crypto/bcrypt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
)
func NewOperator(ksClient kubesphereclient.Interface, informer informers.SharedInformerFactory) IdentityManagementInterface {
return &defaultIMOperator{
ksClient: ksClient,
informer: informer,
}
}
type defaultIMOperator struct {
ksClient kubesphereclient.Interface
informer informers.SharedInformerFactory
}
func (im *defaultIMOperator) ModifyUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
return im.ksClient.IamV1alpha2().Users().Update(user)
}
func (im *defaultIMOperator) Authenticate(username, password string) (*iamv1alpha2.User, error) {
user, err := im.ksClient.IamV1alpha2().Users().Get(username, metav1.GetOptions{})
if err != nil {
return nil, err
}
if checkPasswordHash(password, user.Spec.EncryptedPassword) {
return user, nil
}
return nil, AuthFailedIncorrectPassword
}
func checkPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func (im *defaultIMOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
user, err := im.ksClient.IamV1alpha2().Users().Get(username, metav1.GetOptions{})
if err != nil {
return nil, err
}
return user, nil
}
func (im *defaultIMOperator) DeleteUser(username string) error {
return im.ksClient.IamV1alpha2().Users().Delete(username, metav1.NewDeleteOptions(0))
}
func (im *defaultIMOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
user, err := im.ksClient.IamV1alpha2().Users().Create(user)
if err != nil {
return nil, err
}
return user, nil
}

View File

@@ -17,3 +17,25 @@
*/
package im
import (
"golang.org/x/crypto/bcrypt"
"testing"
)
func TestEncryptPassword(t *testing.T) {
password := "P@88w0rd"
encryptedPassword, err := hashPassword(password)
if err != nil {
t.Fatal(err)
}
if !checkPasswordHash(password, encryptedPassword) {
t.Fatal(err)
}
t.Log(encryptedPassword)
}
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
return string(bytes), err
}

View File

@@ -0,0 +1,80 @@
/*
*
* Copyright 2020 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 im
import (
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
)
type ldapOperator struct {
ldapClient ldap.Interface
}
func NewLDAPOperator(ldapClient ldap.Interface) IdentityManagementInterface {
return &ldapOperator{
ldapClient: ldapClient,
}
}
func (im *ldapOperator) ModifyUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
err := im.ldapClient.Update(user)
if err != nil {
return nil, err
}
return im.ldapClient.Get(user.Name)
}
func (im *ldapOperator) Authenticate(username, password string) (*iamv1alpha2.User, error) {
user, err := im.ldapClient.Get(username)
if err != nil {
return nil, err
}
err = im.ldapClient.Authenticate(user.Name, password)
if err != nil {
return nil, err
}
return user, nil
}
func (im *ldapOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
return im.ldapClient.Get(username)
}
func (im *ldapOperator) DeleteUser(username string) error {
return im.ldapClient.Delete(username)
}
func (im *ldapOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
err := im.ldapClient.Create(user)
if err != nil {
return nil, err
}
return user, nil
}

View File

@@ -32,9 +32,6 @@ import (
)
const (
applicationLabel = "app.kubernetes.io/name"
ReleaseLabel = "relase"
statusStopped = "stopped"
statusRunning = "running"
statusUpdating = "updating"
@@ -80,14 +77,10 @@ func (d *deploymentsGetter) compare(left runtime.Object, right runtime.Object, f
}
switch field {
case query.FieldCreationTimeStamp:
return leftDeployment.CreationTimestamp.After(rightDeployment.CreationTimestamp.Time)
case query.FieldLastUpdateTimestamp:
return lastUpdateTime(leftDeployment).After(lastUpdateTime(rightDeployment))
default:
fallthrough
case query.FieldName:
return strings.Compare(leftDeployment.Name, rightDeployment.Name) > 0
return v1alpha3.DefaultObjectMetaCompare(leftDeployment.ObjectMeta, rightDeployment.ObjectMeta, field)
}
}
@@ -98,18 +91,12 @@ func (d *deploymentsGetter) filter(object runtime.Object, filter query.Filter) b
}
switch filter.Field {
case query.FieldName:
return query.ComparableString(deployment.Name).Contains(filter.Value)
case query.FieldApplication:
if app, ok := deployment.Labels[applicationLabel]; ok {
return query.ComparableString(app).Contains(filter.Value)
}
case query.FieldStatus:
return filter.Value.Compare(query.ComparableString(deploymentStatus(deployment.Status))) == 0
return strings.Compare(deploymentStatus(deployment.Status), string(filter.Value)) == 0
default:
return false
return v1alpha3.DefaultObjectMetaFilter(deployment.ObjectMeta, filter)
}
return false
}
func deploymentStatus(status v1.DeploymentStatus) string {

View File

@@ -94,15 +94,15 @@ func TestListDeployments(t *testing.T) {
},
&query.Query{
Pagination: &query.Pagination{
Limit: 1,
Page: 1,
Limit: 1,
Offset: 1,
},
SortBy: query.FieldName,
Ascending: false,
Filters: []query.Filter{
{
Field: query.FieldName,
Value: query.ComparableString("foo"),
Value: query.Value("foo"),
},
},
},

View File

@@ -1,10 +1,12 @@
package v1alpha3
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"sort"
"strings"
)
type Interface interface {
@@ -36,14 +38,6 @@ func DefaultList(objects []runtime.Object, query *query.Query, compareFunc Compa
}
}
start, end := query.Pagination.GetPaginationSettings(len(filtered))
if !query.Pagination.IsPageAvailable(len(filtered), start) {
return &api.ListResult{
Items: nil,
TotalItems: 0,
}
}
// sort by sortBy field
sort.Slice(filtered, func(i, j int) bool {
if !query.Ascending {
@@ -52,14 +46,87 @@ func DefaultList(objects []runtime.Object, query *query.Query, compareFunc Compa
return compareFunc(filtered[i], filtered[j], query.SortBy)
})
total := len(filtered)
start, end := query.Pagination.GetValidPagination(total)
return &api.ListResult{
Items: objectsToInterfaces(filtered[start:end]),
TotalItems: len(filtered),
Items: objectsToInterfaces(filtered[start:end]),
}
}
func DefaultObjectMetaCompare(left, right metav1.ObjectMeta, sortBy query.Field) bool {
switch sortBy {
// ?sortBy=name
case query.FieldName:
return strings.Compare(left.Name, right.Name) > 0
// ?sortBy=creationTimestamp
case query.FieldCreationTimeStamp:
return left.CreationTimestamp.After(right.CreationTimestamp.Time)
default:
return false
}
}
// Default metadata filter
func DefaultObjectMetaFilter(item metav1.ObjectMeta, filter query.Filter) bool {
switch filter.Field {
// /namespaces?page=1&limit=10&name=default
case query.FieldName:
return strings.Contains(item.Name, string(filter.Value))
// /namespaces?page=1&limit=10&uid=a8a8d6cf-f6a5-4fea-9c1b-e57610115706
case query.FieldUID:
return strings.Compare(string(item.UID), string(filter.Value)) == 0
// /deployments?page=1&limit=10&namespace=kubesphere-system
case query.FieldNamespace:
return strings.Compare(item.Namespace, string(filter.Value)) == 0
// /namespaces?page=1&limit=10&ownerReference=a8a8d6cf-f6a5-4fea-9c1b-e57610115706
case query.FieldOwnerReference:
for _, ownerReference := range item.OwnerReferences {
if strings.Compare(string(ownerReference.UID), string(filter.Value)) == 0 {
return true
}
}
return false
// /namespaces?page=1&limit=10&ownerKind=Workspace
case query.FieldOwnerKind:
for _, ownerReference := range item.OwnerReferences {
if strings.Compare(ownerReference.Kind, string(filter.Value)) == 0 {
return true
}
}
return false
// /namespaces?page=1&limit=10&annotation=openpitrix_runtime
case query.FieldAnnotation:
return containsAnyValue(item.Annotations, string(filter.Value))
// /namespaces?page=1&limit=10&label=kubesphere.io/workspace:system-workspace
case query.FieldLabel:
return containsAnyValue(item.Labels, string(filter.Value))
default:
return false
}
}
// Filter format <key:><value>,if the key is defined, the key must match exactly, value match according to strings.Contains.
func containsAnyValue(keyValues map[string]string, filter string) bool {
fields := strings.SplitN(filter, ":", 2)
var keyFilter, valueFilter string
if len(fields) == 2 {
keyFilter = fields[0]
valueFilter = fields[1]
} else {
valueFilter = fields[0]
}
for key, value := range keyValues {
if (key == keyFilter || keyFilter == "") && strings.Contains(value, valueFilter) {
return true
}
}
return false
}
func objectsToInterfaces(objs []runtime.Object) []interface{} {
var res []interface{}
res := make([]interface{}, 0)
for _, obj := range objs {
res = append(res, obj)
}

View File

@@ -15,7 +15,7 @@ type namespaceGetter struct {
informers informers.SharedInformerFactory
}
func NewNamespaceGetter(informers informers.SharedInformerFactory) v1alpha3.Interface {
func New(informers informers.SharedInformerFactory) v1alpha3.Interface {
return &namespaceGetter{informers: informers}
}
@@ -42,14 +42,11 @@ func (n namespaceGetter) filter(item runtime.Object, filter query.Filter) bool {
if !ok {
return false
}
switch filter.Field {
case query.FieldName:
return query.ComparableString(namespace.Name).Contains(filter.Value)
case query.FieldStatus:
return query.ComparableString(namespace.Status.Phase).Compare(filter.Value) == 0
return strings.Compare(string(namespace.Status.Phase), string(filter.Value)) == 0
default:
return false
return v1alpha3.DefaultObjectMetaFilter(namespace.ObjectMeta, filter)
}
}
@@ -63,13 +60,5 @@ func (n namespaceGetter) compare(left runtime.Object, right runtime.Object, fiel
if !ok {
return true
}
switch field {
case query.FieldName:
return strings.Compare(leftNs.Name, rightNs.Name) > 0
case query.FieldCreationTimeStamp:
return leftNs.CreationTimestamp.After(rightNs.CreationTimestamp.Time)
default:
return false
}
return v1alpha3.DefaultObjectMetaCompare(leftNs.ObjectMeta, rightNs.ObjectMeta, field)
}

View File

@@ -8,27 +8,29 @@ import (
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/deployment"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/namespace"
)
var ErrResourceNotSupported = errors.New("resource is not supported")
type NamespacedResourceGetter struct {
type ResourceGetter struct {
getters map[schema.GroupVersionResource]v1alpha3.Interface
}
func New(factory informers.InformerFactory) *NamespacedResourceGetter {
func New(factory informers.InformerFactory) *ResourceGetter {
getters := make(map[schema.GroupVersionResource]v1alpha3.Interface)
getters[schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}] = deployment.New(factory.KubernetesSharedInformerFactory())
getters[schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"}] = namespace.New(factory.KubernetesSharedInformerFactory())
return &NamespacedResourceGetter{
return &ResourceGetter{
getters: getters,
}
}
// tryResource will retrieve a getter with resource name, it doesn't guarantee find resource with correct group version
// need to refactor this use schema.GroupVersionResource
func (r *NamespacedResourceGetter) tryResource(resource string) v1alpha3.Interface {
func (r *ResourceGetter) tryResource(resource string) v1alpha3.Interface {
for k, v := range r.getters {
if k.Resource == resource {
return v
@@ -38,21 +40,18 @@ func (r *NamespacedResourceGetter) tryResource(resource string) v1alpha3.Interfa
return nil
}
func (r *NamespacedResourceGetter) Get(resource, namespace, name string) (interface{}, error) {
func (r *ResourceGetter) Get(resource, namespace, name string) (interface{}, error) {
getter := r.tryResource(resource)
if getter == nil {
return nil, ErrResourceNotSupported
}
return getter.Get(namespace, name)
}
func (r *NamespacedResourceGetter) List(resource, namespace string, query *query.Query) (*api.ListResult, error) {
func (r *ResourceGetter) List(resource, namespace string, query *query.Query) (*api.ListResult, error) {
getter := r.tryResource(resource)
if getter == nil {
return nil, ErrResourceNotSupported
}
return getter.List(namespace, query)
}

View File

@@ -0,0 +1,111 @@
/*
*
* Copyright 2020 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 resource
import (
"github.com/google/go-cmp/cmp"
fakeapp "github.com/kubernetes-sigs/application/pkg/client/clientset/versioned/fake"
fakeistio "istio.io/client-go/pkg/clientset/versioned/fake"
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/apimachinery/pkg/apis/meta/v1"
fakek8s "k8s.io/client-go/kubernetes/fake"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
"kubesphere.io/kubesphere/pkg/informers"
"testing"
)
func TestResourceGetter(t *testing.T) {
namespaces := make([]interface{}, 0)
defaultNamespace := &v1.Namespace{
ObjectMeta: corev1.ObjectMeta{
Name: "default",
Labels: map[string]string{"kubesphere.io/workspace": "system-workspace"},
},
}
kubesphereNamespace := &v1.Namespace{
ObjectMeta: corev1.ObjectMeta{
Name: "kubesphere-system",
Labels: map[string]string{"kubesphere.io/workspace": "system-workspace"},
},
}
namespaces = append(namespaces, defaultNamespace, kubesphereNamespace)
ksClient := fakeks.NewSimpleClientset()
k8sClient := fakek8s.NewSimpleClientset(defaultNamespace, kubesphereNamespace)
istioClient := fakeistio.NewSimpleClientset()
appClient := fakeapp.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, istioClient, appClient)
k8sInformerFactory := fakeInformerFactory.KubernetesSharedInformerFactory()
for _, namespace := range namespaces {
err := k8sInformerFactory.Core().V1().Namespaces().Informer().GetIndexer().Add(namespace)
if err != nil {
t.Fatal(err)
}
}
resource := New(fakeInformerFactory)
tests := []struct {
Name string
Resource string
Namespace string
Query *query.Query
ExpectError error
ExpectResponse *api.ListResult
}{
{
Name: "normal case",
Resource: "namespaces",
Namespace: "",
Query: &query.Query{
Pagination: &query.Pagination{
Limit: 10,
Offset: 0,
},
SortBy: query.FieldName,
Ascending: false,
Filters: []query.Filter{},
},
ExpectError: nil,
ExpectResponse: &api.ListResult{
Items: namespaces,
TotalItems: 2,
},
},
}
for _, test := range tests {
result, err := resource.List(test.Resource, test.Namespace, test.Query)
t.Logf("%+v", result)
if err != test.ExpectError {
t.Errorf("expected error: %s, got: %s", test.ExpectError, err)
}
if diff := cmp.Diff(test.ExpectResponse, result); diff != "" {
t.Errorf(diff)
}
}
}

View File

@@ -1,225 +0,0 @@
/*
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 tenant
import (
"fmt"
"github.com/emicklei/go-restful"
"github.com/gocraft/dbr"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api/devops/v1alpha2"
"kubesphere.io/kubesphere/pkg/db"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/server/params"
dsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"net/http"
)
type DevOpsProjectOperator interface {
ListDevOpsProjects(workspace, username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error)
CreateDevOpsProject(username string, workspace string, req *v1alpha2.DevOpsProject) (*v1alpha2.DevOpsProject, error)
GetDevOpsProjectsCount(username string) (uint32, error)
DeleteDevOpsProject(projectId, username string) error
}
type devopsProjectOperator struct {
ksProjectOperator devops.ProjectOperator
db *mysql.Database
dsProject dsClient.ProjectOperator
}
func newProjectOperator(operator devops.ProjectOperator, db *mysql.Database, client dsClient.ProjectOperator) DevOpsProjectOperator {
return &devopsProjectOperator{
ksProjectOperator: operator,
db: db,
dsProject: client,
}
}
func (o *devopsProjectOperator) ListDevOpsProjects(workspace, username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) {
query := o.db.Select(devops.GetColumnsFromStructWithPrefix(devops.DevOpsProjectTableName, v1alpha2.DevOpsProject{})...).
From(devops.DevOpsProjectTableName)
var sqconditions []dbr.Builder
sqconditions = append(sqconditions, db.Eq(devops.DevOpsProjectWorkSpaceColumn, workspace))
switch username {
case devops.KS_ADMIN:
default:
onCondition := fmt.Sprintf("%s = %s", devops.ProjectMembershipProjectIdColumn, devops.DevOpsProjectIdColumn)
query.Join(devops.ProjectMembershipTableName, onCondition)
sqconditions = append(sqconditions, db.Eq(devops.ProjectMembershipUsernameColumn, username))
sqconditions = append(sqconditions, db.Eq(
devops.ProjectMembershipTableName+"."+devops.StatusColumn, devops.StatusActive))
}
sqconditions = append(sqconditions, db.Eq(
devops.DevOpsProjectTableName+"."+devops.StatusColumn, devops.StatusActive))
if keyword := conditions.Match["keyword"]; keyword != "" {
sqconditions = append(sqconditions, db.Like(devops.DevOpsProjectNameColumn, keyword))
}
projects := make([]*v1alpha2.DevOpsProject, 0)
if len(sqconditions) > 0 {
query.Where(db.And(sqconditions...))
}
switch orderBy {
case "name":
if reverse {
query.OrderDesc(devops.DevOpsProjectNameColumn)
} else {
query.OrderAsc(devops.DevOpsProjectNameColumn)
}
default:
if reverse {
query.OrderAsc(devops.DevOpsProjectCreateTimeColumn)
} else {
query.OrderDesc(devops.DevOpsProjectCreateTimeColumn)
}
}
query.Limit(uint64(limit))
query.Offset(uint64(offset))
_, err := query.Load(&projects)
if err != nil {
klog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
count, err := query.Count()
if err != nil {
klog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
result := make([]interface{}, 0)
for _, v := range projects {
result = append(result, v)
}
return &models.PageableResponse{Items: result, TotalCount: int(count)}, nil
}
func (o *devopsProjectOperator) GetDevOpsProjectsCount(username string) (uint32, error) {
query := o.db.Select(devops.GetColumnsFromStructWithPrefix(devops.DevOpsProjectTableName, v1alpha2.DevOpsProject{})...).
From(devops.DevOpsProjectTableName)
var sqconditions []dbr.Builder
if username != devops.KS_ADMIN {
onCondition := fmt.Sprintf("%s = %s", devops.ProjectMembershipProjectIdColumn, devops.DevOpsProjectIdColumn)
query.Join(devops.ProjectMembershipTableName, onCondition)
sqconditions = append(sqconditions, db.Eq(devops.ProjectMembershipUsernameColumn, username))
sqconditions = append(sqconditions, db.Eq(
devops.ProjectMembershipTableName+"."+devops.StatusColumn, devops.StatusActive))
}
sqconditions = append(sqconditions, db.Eq(
devops.DevOpsProjectTableName+"."+devops.StatusColumn, devops.StatusActive))
if len(sqconditions) > 0 {
query.Where(db.And(sqconditions...))
}
count, err := query.Count()
if err != nil {
klog.Errorf("%+v", err)
return 0, restful.NewError(http.StatusInternalServerError, err.Error())
}
return count, nil
}
func (o *devopsProjectOperator) DeleteDevOpsProject(projectId, username string) error {
err := o.ksProjectOperator.CheckProjectUserInRole(username, projectId, []string{dsClient.ProjectOwner})
if err != nil {
klog.Errorf("%+v", err)
return restful.NewError(http.StatusForbidden, err.Error())
}
err = o.dsProject.DeleteDevOpsProject(projectId)
if err != nil {
klog.Errorf("%+v", err)
return err
}
_, err = o.db.DeleteFrom(devops.ProjectMembershipTableName).
Where(db.Eq(devops.ProjectMembershipProjectIdColumn, projectId)).Exec()
if err != nil {
klog.Errorf("%+v", err)
return err
}
_, err = o.db.Update(devops.DevOpsProjectTableName).
Set(devops.StatusColumn, devops.StatusDeleted).
Where(db.Eq(devops.DevOpsProjectIdColumn, projectId)).Exec()
if err != nil {
klog.Errorf("%+v", err)
return err
}
project := &v1alpha2.DevOpsProject{}
err = o.db.Select(devops.DevOpsProjectColumns...).
From(devops.DevOpsProjectTableName).
Where(db.Eq(devops.DevOpsProjectIdColumn, projectId)).
LoadOne(project)
if err != nil {
klog.Errorf("%+v", err)
return err
}
return nil
}
func (o *devopsProjectOperator) CreateDevOpsProject(username string, workspace string, req *v1alpha2.DevOpsProject) (*v1alpha2.DevOpsProject, error) {
project := devops.NewDevOpsProject(req.Name, req.Description, username, req.Extra, workspace)
_, err := o.dsProject.CreateDevOpsProject(username, project)
if err != nil {
klog.Error(err)
return nil, err
}
_, err = o.db.InsertInto(devops.DevOpsProjectTableName).
Columns(devops.DevOpsProjectColumns...).Record(project).Exec()
if err != nil {
klog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
projectMembership := devops.NewDevOpsProjectMemberShip(username, project.ProjectId, dsClient.ProjectOwner, username)
_, err = o.db.InsertInto(devops.ProjectMembershipTableName).
Columns(devops.ProjectMembershipColumns...).Record(projectMembership).Exec()
if err != nil {
klog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
return project, nil
}
func (o *devopsProjectOperator) getProjectUserRole(username, projectId string) (string, error) {
if username == devops.KS_ADMIN {
return dsClient.ProjectOwner, nil
}
membership := &dsClient.ProjectMembership{}
err := o.db.Select(devops.ProjectMembershipColumns...).
From(devops.ProjectMembershipTableName).
Where(db.And(
db.Eq(devops.ProjectMembershipUsernameColumn, username),
db.Eq(devops.ProjectMembershipProjectIdColumn, projectId))).LoadOne(membership)
if err != nil {
return "", err
}
return membership.Role, nil
}

View File

@@ -1,115 +0,0 @@
/*
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 tenant
import (
"k8s.io/api/core/v1"
k8sinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"strings"
)
type NamespaceInterface interface {
Search(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1.Namespace, error)
CreateNamespace(workspace string, namespace *v1.Namespace, username string) (*v1.Namespace, error)
}
type namespaceSearcher struct {
k8s kubernetes.Interface
informers k8sinformers.SharedInformerFactory
am am.AccessManagementInterface
}
func (s *namespaceSearcher) CreateNamespace(workspace string, namespace *v1.Namespace, username string) (*v1.Namespace, error) {
if namespace.Labels == nil {
namespace.Labels = make(map[string]string, 0)
}
if username != "" {
namespace.Annotations[constants.CreatorAnnotationKey] = username
}
namespace.Labels[constants.WorkspaceLabelKey] = workspace
return s.k8s.CoreV1().Namespaces().Create(namespace)
}
func newNamespaceOperator(k8s kubernetes.Interface, informers k8sinformers.SharedInformerFactory, am am.AccessManagementInterface) NamespaceInterface {
return &namespaceSearcher{k8s: k8s, informers: informers, am: am}
}
func (s *namespaceSearcher) match(match map[string]string, item *v1.Namespace) bool {
for k, v := range match {
switch k {
case v1alpha2.Name:
names := strings.Split(v, "|")
if !sliceutil.HasString(names, item.Name) {
return false
}
case v1alpha2.Keyword:
if !strings.Contains(item.Name, v) && !contains(item.Labels, "", v) && !contains(item.Annotations, "", v) {
return false
}
default:
// label not exist or value not equal
if val, ok := item.Labels[k]; !ok || val != v {
return false
}
}
}
return true
}
func (s *namespaceSearcher) fuzzy(fuzzy map[string]string, item *v1.Namespace) bool {
for k, v := range fuzzy {
switch k {
case v1alpha2.Name:
if !strings.Contains(item.Name, v) && !strings.Contains(item.Annotations[constants.DisplayNameAnnotationKey], v) {
return false
}
default:
return false
}
}
return true
}
func (s *namespaceSearcher) compare(a, b *v1.Namespace, orderBy string) bool {
switch orderBy {
case "createTime":
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case "name":
fallthrough
default:
return strings.Compare(a.Name, b.Name) <= 0
}
}
func (s *namespaceSearcher) GetNamespaces(username string) ([]*v1.Namespace, error) {
panic("implement me")
}
func (s *namespaceSearcher) Search(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1.Namespace, error) {
panic("implement me")
}

View File

@@ -18,97 +18,147 @@
package tenant
import (
"k8s.io/api/core/v1"
k8sinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/models"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
)
type Interface interface {
CreateNamespace(workspace string, namespace *v1.Namespace, username string) (*v1.Namespace, error)
DeleteNamespace(workspace, namespace string) error
DescribeWorkspace(username, workspace string) (*v1alpha1.Workspace, error)
ListWorkspaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
ListNamespaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
ListDevopsProjects(username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error)
CountDevOpsProjects(username string) (uint32, error)
DeleteDevOpsProject(username, projectId string) error
ListWorkspaces(username string) (*api.ListResult, error)
ListNamespaces(username, workspace string) (*api.ListResult, error)
}
type tenantOperator struct {
workspaces WorkspaceInterface
namespaces NamespaceInterface
am am.AccessManagementInterface
devops DevOpsProjectOperator
informers informers.InformerFactory
am am.AccessManagementInterface
}
func (t *tenantOperator) CountDevOpsProjects(username string) (uint32, error) {
return t.devops.GetDevOpsProjectsCount(username)
}
func (t *tenantOperator) DeleteDevOpsProject(username, projectId string) error {
return t.devops.DeleteDevOpsProject(projectId, username)
}
func (t *tenantOperator) GetUserDevopsSimpleRules(username string, projectId string) (interface{}, error) {
panic("implement me")
}
func (t *tenantOperator) ListDevopsProjects(username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) {
return t.devops.ListDevOpsProjects(conditions.Match["workspace"], username, conditions, orderBy, reverse, limit, offset)
}
func (t *tenantOperator) DeleteNamespace(workspace, namespace string) error {
return t.workspaces.DeleteNamespace(workspace, namespace)
}
func New(client kubernetes.Interface, informers k8sinformers.SharedInformerFactory, ksinformers ksinformers.SharedInformerFactory, db *mysql.Database) Interface {
amOperator := am.NewAMOperator(client, informers)
func New(k8sClient k8s.Client, informers informers.InformerFactory) Interface {
return &tenantOperator{
workspaces: newWorkspaceOperator(client, informers, ksinformers, amOperator, db),
namespaces: newNamespaceOperator(client, informers, amOperator),
am: amOperator,
informers: informers,
am: am.NewAMOperator(k8sClient.KubeSphere(), informers.KubeSphereSharedInformerFactory()),
}
}
func (t *tenantOperator) CreateNamespace(workspaceName string, namespace *v1.Namespace, username string) (*v1.Namespace, error) {
return t.namespaces.CreateNamespace(workspaceName, namespace, username)
}
func (t *tenantOperator) DescribeWorkspace(username, workspaceName string) (*v1alpha1.Workspace, error) {
workspace, err := t.workspaces.GetWorkspace(workspaceName)
func (t *tenantOperator) ListWorkspaces(username string) (*api.ListResult, error) {
workspaceRoles, err := t.am.ListRolesOfUser(iamv1alpha2.WorkspaceScope, username)
if err != nil {
klog.Error(err)
return nil, err
}
return workspace, nil
}
workspaces := make([]*tenantv1alpha1.Workspace, 0)
func (t *tenantOperator) ListWorkspaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
panic("implement me")
}
for _, role := range workspaceRoles {
func (t *tenantOperator) ListNamespaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
// all workspaces are allowed
if role.Target.Name == iamv1alpha2.TargetAll {
workspaces, err = t.informers.KubeSphereSharedInformerFactory().
Tenant().V1alpha1().Workspaces().Lister().List(labels.Everything())
break
}
workspace, err := t.informers.KubeSphereSharedInformerFactory().
Tenant().V1alpha1().Workspaces().Lister().Get(role.Target.Name)
namespaces, err := t.namespaces.Search(username, conditions, orderBy, reverse)
if err != nil {
return nil, err
}
// limit offset
result := make([]interface{}, 0)
for i, v := range namespaces {
if len(result) < limit && i >= offset {
result = append(result, v)
if errors.IsNotFound(err) {
klog.Warningf("workspace role: %s found but workspace not exist", role.Target)
continue
}
if err != nil {
klog.Error(err)
return nil, err
}
if !containsWorkspace(workspaces, workspace) {
workspaces = append(workspaces, workspace)
}
}
return &models.PageableResponse{Items: result, TotalCount: len(namespaces)}, nil
return &api.ListResult{
TotalItems: len(workspaces),
Items: workspacesToInterfaces(workspaces),
}, nil
}
func (t *tenantOperator) ListNamespaces(username, workspace string) (*api.ListResult, error) {
namespaceRoles, err := t.am.ListRolesOfUser(iamv1alpha2.NamespaceScope, username)
if err != nil {
klog.Error(err)
return nil, err
}
namespaces := make([]*corev1.Namespace, 0)
for _, role := range namespaceRoles {
// all workspaces are allowed
if role.Target.Name == iamv1alpha2.TargetAll {
namespaces, err = t.informers.KubernetesSharedInformerFactory().
Core().V1().Namespaces().Lister().List(labels.Everything())
break
}
namespace, err := t.informers.KubernetesSharedInformerFactory().
Core().V1().Namespaces().Lister().Get(role.Target.Name)
if errors.IsNotFound(err) {
klog.Warningf("workspace role: %s found but workspace not exist", role.Target)
continue
}
if err != nil {
klog.Error(err)
return nil, err
}
if !containsNamespace(namespaces, namespace) {
namespaces = append(namespaces, namespace)
}
}
return &api.ListResult{
TotalItems: len(namespaces),
Items: namespacesToInterfaces(namespaces),
}, nil
}
func containsWorkspace(workspaces []*tenantv1alpha1.Workspace, workspace *tenantv1alpha1.Workspace) bool {
for _, item := range workspaces {
if item.Name == workspace.Name {
return true
}
}
return false
}
func containsNamespace(namespaces []*corev1.Namespace, namespace *corev1.Namespace) bool {
for _, item := range namespaces {
if item.Name == namespace.Name {
return true
}
}
return false
}
func workspacesToInterfaces(workspaces []*tenantv1alpha1.Workspace) []interface{} {
ret := make([]interface{}, len(workspaces))
for index, v := range workspaces {
ret[index] = v
}
return ret
}
func namespacesToInterfaces(namespaces []*corev1.Namespace) []interface{} {
ret := make([]interface{}, len(namespaces))
for index, v := range namespaces {
ret[index] = v
}
return ret
}

View File

@@ -1,264 +0,0 @@
/*
*
* Copyright 2020 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 tenant
import (
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/db"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"strings"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
iamapi "kubesphere.io/kubesphere/pkg/api/iam"
)
type InWorkspaceUser struct {
*iamapi.User
WorkspaceRole string `json:"workspaceRole"`
}
type WorkspaceInterface interface {
GetWorkspace(workspace string) (*v1alpha1.Workspace, error)
SearchWorkspace(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1alpha1.Workspace, error)
ListNamespaces(workspace string) ([]*core.Namespace, error)
DeleteNamespace(workspace, namespace string) error
RemoveUser(user, workspace string) error
AddUser(workspace string, user *InWorkspaceUser) error
CountDevopsProjectsInWorkspace(workspace string) (int, error)
CountUsersInWorkspace(workspace string) (int, error)
CountOrgRoles() (int, error)
CountWorkspaces() (int, error)
CountNamespacesInWorkspace(workspace string) (int, error)
}
type workspaceOperator struct {
client kubernetes.Interface
informers informers.SharedInformerFactory
ksInformers externalversions.SharedInformerFactory
am am.AccessManagementInterface
// TODO: use db interface instead of mysql client
// we can refactor this after rewrite devops using crd
db *mysql.Database
}
func newWorkspaceOperator(client kubernetes.Interface, informers informers.SharedInformerFactory, ksinformers externalversions.SharedInformerFactory, am am.AccessManagementInterface, db *mysql.Database) WorkspaceInterface {
return &workspaceOperator{
client: client,
informers: informers,
ksInformers: ksinformers,
am: am,
db: db,
}
}
func (w *workspaceOperator) ListNamespaces(workspace string) ([]*core.Namespace, error) {
namespaces, err := w.informers.Core().V1().Namespaces().Lister().List(labels.SelectorFromSet(labels.Set{constants.WorkspaceLabelKey: workspace}))
if err != nil {
return nil, err
}
return namespaces, nil
}
func (w *workspaceOperator) DeleteNamespace(workspace string, namespace string) error {
ns, err := w.informers.Core().V1().Namespaces().Lister().Get(namespace)
if err != nil {
return err
}
if ns.Labels[constants.WorkspaceLabelKey] == workspace {
deletePolicy := metav1.DeletePropagationBackground
return w.client.CoreV1().Namespaces().Delete(namespace, &metav1.DeleteOptions{PropagationPolicy: &deletePolicy})
} else {
return apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "workspace"}, workspace)
}
}
func (w *workspaceOperator) RemoveUser(workspace string, username string) error {
panic("implement me")
}
func (w *workspaceOperator) AddUser(workspaceName string, user *InWorkspaceUser) error {
panic("implement me")
}
func (w *workspaceOperator) CountDevopsProjectsInWorkspace(workspaceName string) (int, error) {
query := w.db.Select(devops.DevOpsProjectIdColumn).
From(devops.DevOpsProjectTableName).
Where(db.And(db.Eq(devops.DevOpsProjectWorkSpaceColumn, workspaceName),
db.Eq(devops.StatusColumn, devops.StatusActive)))
devOpsProjects := make([]string, 0)
if _, err := query.Load(&devOpsProjects); err != nil {
return 0, err
}
return len(devOpsProjects), nil
}
func (w *workspaceOperator) CountUsersInWorkspace(workspace string) (int, error) {
count, err := w.CountUsersInWorkspace(workspace)
if err != nil {
return 0, err
}
return count, nil
}
func (w *workspaceOperator) CountOrgRoles() (int, error) {
return len(constants.WorkSpaceRoles), nil
}
func (w *workspaceOperator) CountNamespacesInWorkspace(workspace string) (int, error) {
ns, err := w.ListNamespaces(workspace)
if err != nil {
return 0, err
}
return len(ns), nil
}
func (*workspaceOperator) match(match map[string]string, item *v1alpha1.Workspace) bool {
for k, v := range match {
switch k {
case v1alpha2.Name:
names := strings.Split(v, "|")
if !sliceutil.HasString(names, item.Name) {
return false
}
case v1alpha2.Keyword:
if !strings.Contains(item.Name, v) && !contains(item.Labels, "", v) && !contains(item.Annotations, "", v) {
return false
}
default:
// label not exist or value not equal
if val, ok := item.Labels[k]; !ok || val != v {
return false
}
}
}
return true
}
func (*workspaceOperator) fuzzy(fuzzy map[string]string, item *v1alpha1.Workspace) bool {
for k, v := range fuzzy {
switch k {
case v1alpha2.Name:
if !strings.Contains(item.Name, v) && !strings.Contains(item.Annotations[constants.DisplayNameAnnotationKey], v) {
return false
}
default:
return false
}
}
return true
}
func (*workspaceOperator) compare(a, b *v1alpha1.Workspace, orderBy string) bool {
switch orderBy {
case v1alpha2.CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case v1alpha2.Name:
fallthrough
default:
return strings.Compare(a.Name, b.Name) <= 0
}
}
func (w *workspaceOperator) SearchWorkspace(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1alpha1.Workspace, error) {
panic("implement me")
}
func (w *workspaceOperator) GetWorkspace(workspaceName string) (*v1alpha1.Workspace, error) {
return w.ksInformers.Tenant().V1alpha1().Workspaces().Lister().Get(workspaceName)
}
func contains(m map[string]string, key, value string) bool {
for k, v := range m {
if key == "" {
if strings.Contains(k, value) || strings.Contains(v, value) {
return true
}
} else if k == key && strings.Contains(v, value) {
return true
}
}
return false
}
/*
// TODO: move to metrics package
func GetAllProjectNums() (int, error) {
namespaceLister := informers.SharedInformerFactory().Core().V1().Namespaces().Lister()
list, err := namespaceLister.List(labels.Everything())
if err != nil {
return 0, err
}
return len(list), nil
}
func GetAllDevOpsProjectsNums() (int, error) {
_, err := clientset.ClientSets().Devops()
if _, notEnabled := err.(clientset.ClientSetNotEnabledError); notEnabled {
return 0, err
}
dbconn, err := clientset.ClientSets().MySQL()
if err != nil {
return 0, err
}
query := dbconn.Select(devops.DevOpsProjectIdColumn).
From(devops.DevOpsProjectTableName).
Where(db.Eq(devops.StatusColumn, devops.StatusActive))
devOpsProjects := make([]string, 0)
if _, err := query.Load(&devOpsProjects); err != nil {
return 0, err
}
return len(devOpsProjects), nil
}
*/
func (w *workspaceOperator) CountWorkspaces() (int, error) {
ws, err := w.ksInformers.Tenant().V1alpha1().Workspaces().Lister().List(labels.Everything())
if err != nil {
return 0, err
}
return len(ws), nil
}

View File

@@ -1,20 +1,22 @@
package ldap
import "kubesphere.io/kubesphere/pkg/api/iam"
import (
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
)
// Interface defines CRUD behaviors of manipulating users
type Interface interface {
// Create create a new user in ldap
Create(user *iam.User) error
Create(user *iamv1alpha2.User) error
// Update updates a user information, return error if user not exists
Update(user *iam.User) error
Update(user *iamv1alpha2.User) error
// Delete deletes a user from ldap, return nil if user not exists
Delete(name string) error
// Get gets a user by its username from ldap, return ErrUserNotExists if user not exists
Get(name string) (*iam.User, error)
Get(name string) (*iamv1alpha2.User, error)
// Authenticate checks if (name, password) is valid, return ErrInvalidCredentials if not
Authenticate(name string, password string) error

View File

@@ -21,7 +21,8 @@ import (
"fmt"
"github.com/go-ldap/ldap"
"github.com/google/uuid"
"kubesphere.io/kubesphere/pkg/api/iam"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/server/errors"
"sync"
"time"
@@ -181,7 +182,7 @@ func (l *ldapInterfaceImpl) filterForUsername(username string) string {
return fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(uid=%s)(mail=%s)))", username, username)
}
func (l *ldapInterfaceImpl) Get(name string) (*iam.User, error) {
func (l *ldapInterfaceImpl) Get(name string) (*iamv1alpha2.User, error) {
conn, err := l.newConn()
if err != nil {
return nil, err
@@ -215,20 +216,24 @@ func (l *ldapInterfaceImpl) Get(name string) (*iam.User, error) {
userEntry := searchResults.Entries[0]
user := &iam.User{
Name: userEntry.GetAttributeValue(ldapAttributeUserID),
Email: userEntry.GetAttributeValue(ldapAttributeMail),
Lang: userEntry.GetAttributeValue(ldapAttributePreferredLanguage),
Description: userEntry.GetAttributeValue(ldapAttributeDescription),
user := &iamv1alpha2.User{
ObjectMeta: metav1.ObjectMeta{
Name: userEntry.GetAttributeValue(ldapAttributeUserID),
},
Spec: iamv1alpha2.UserSpec{
Email: userEntry.GetAttributeValue(ldapAttributeMail),
Lang: userEntry.GetAttributeValue(ldapAttributePreferredLanguage),
Description: userEntry.GetAttributeValue(ldapAttributeDescription),
},
}
createTimestamp, _ := time.Parse(ldapAttributeCreateTimestampLayout, userEntry.GetAttributeValue(ldapAttributeCreateTimestamp))
user.CreateTime = createTimestamp
user.ObjectMeta.CreationTimestamp.Time = createTimestamp
return user, nil
}
func (l *ldapInterfaceImpl) Create(user *iam.User) error {
func (l *ldapInterfaceImpl) Create(user *iamv1alpha2.User) error {
if _, err := l.Get(user.Name); err != nil {
return ErrUserAlreadyExisted
}
@@ -266,19 +271,19 @@ func (l *ldapInterfaceImpl) Create(user *iam.User) error {
},
{
Type: ldapAttributeMail,
Vals: []string{user.Email},
Vals: []string{user.Spec.Email},
},
{
Type: ldapAttributeUserPassword,
Vals: []string{user.Password},
Vals: []string{user.Spec.EncryptedPassword},
},
{
Type: ldapAttributePreferredLanguage,
Vals: []string{user.Lang},
Vals: []string{user.Spec.Lang},
},
{
Type: ldapAttributeDescription,
Vals: []string{user.Description},
Vals: []string{user.Spec.Description},
},
},
}
@@ -314,7 +319,7 @@ func (l *ldapInterfaceImpl) Delete(name string) error {
return conn.Del(deleteRequest)
}
func (l *ldapInterfaceImpl) Update(newUser *iam.User) error {
func (l *ldapInterfaceImpl) Update(newUser *iamv1alpha2.User) error {
conn, err := l.newConn()
if err != nil {
return err
@@ -331,16 +336,16 @@ func (l *ldapInterfaceImpl) Update(newUser *iam.User) error {
DN: l.dnForUsername(newUser.Name),
}
if newUser.Description != "" {
modifyRequest.Replace(ldapAttributeDescription, []string{newUser.Description})
if newUser.Spec.Description != "" {
modifyRequest.Replace(ldapAttributeDescription, []string{newUser.Spec.Description})
}
if newUser.Lang != "" {
modifyRequest.Replace(ldapAttributePreferredLanguage, []string{newUser.Lang})
if newUser.Spec.Lang != "" {
modifyRequest.Replace(ldapAttributePreferredLanguage, []string{newUser.Spec.Lang})
}
if newUser.Password != "" {
modifyRequest.Replace(ldapAttributeUserPassword, []string{newUser.Password})
if newUser.Spec.EncryptedPassword != "" {
modifyRequest.Replace(ldapAttributeUserPassword, []string{newUser.Spec.EncryptedPassword})
}
return conn.Modify(modifyRequest)

View File

@@ -1,40 +1,43 @@
package ldap
import (
"kubesphere.io/kubesphere/pkg/api/iam"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
)
// simpleLdap is a implementation of ldap.Interface, you should never use this in production env!
type simpleLdap struct {
store map[string]*iam.User
store map[string]*iamv1alpha2.User
}
func NewSimpleLdap() Interface {
sl := &simpleLdap{
store: map[string]*iam.User{},
store: map[string]*iamv1alpha2.User{},
}
// initialize with a admin user
admin := &iam.User{
Name: "admin",
Email: "admin@kubesphere.io",
Lang: "eng",
Description: "administrator",
CreateTime: time.Now(),
Groups: nil,
Password: "P@88w0rd",
admin := &iamv1alpha2.User{
ObjectMeta: metav1.ObjectMeta{
Name: "admin",
},
Spec: iamv1alpha2.UserSpec{
Email: "admin@kubesphere.io",
Lang: "eng",
Description: "administrator",
Groups: nil,
EncryptedPassword: "P@88w0rd",
},
}
sl.store[admin.Name] = admin
return sl
}
func (s simpleLdap) Create(user *iam.User) error {
func (s simpleLdap) Create(user *iamv1alpha2.User) error {
s.store[user.Name] = user
return nil
}
func (s simpleLdap) Update(user *iam.User) error {
func (s simpleLdap) Update(user *iamv1alpha2.User) error {
_, err := s.Get(user.Name)
if err != nil {
return err
@@ -52,7 +55,7 @@ func (s simpleLdap) Delete(name string) error {
return nil
}
func (s simpleLdap) Get(name string) (*iam.User, error) {
func (s simpleLdap) Get(name string) (*iamv1alpha2.User, error) {
if user, ok := s.store[name]; !ok {
return nil, ErrUserNotExists
} else {
@@ -64,7 +67,7 @@ func (s simpleLdap) Authenticate(name string, password string) error {
if user, err := s.Get(name); err != nil {
return err
} else {
if user.Password != password {
if user.Spec.EncryptedPassword != password {
return ErrInvalidCredentials
}
}

View File

@@ -2,22 +2,26 @@ package ldap
import (
"github.com/google/go-cmp/cmp"
"kubesphere.io/kubesphere/pkg/api/iam"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"testing"
"time"
)
func TestSimpleLdap(t *testing.T) {
ldapClient := NewSimpleLdap()
foo := &iam.User{
Name: "jerry",
Email: "jerry@kubesphere.io",
Lang: "en",
Description: "Jerry is kind and gentle.",
CreateTime: time.Now(),
Groups: []string{},
Password: "P@88w0rd",
foo := &iamv1alpha2.User{
TypeMeta: metav1.TypeMeta{APIVersion: iamv1alpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "jerry",
},
Spec: iamv1alpha2.UserSpec{
Email: "jerry@kubesphere.io",
Lang: "en",
Description: "Jerry is kind and gentle.",
Groups: []string{},
EncryptedPassword: "P@88w0rd",
},
}
t.Run("should create user", func(t *testing.T) {
@@ -44,7 +48,7 @@ func TestSimpleLdap(t *testing.T) {
t.Fatal(err)
}
foo.Description = "Jerry needs some drinks."
foo.Spec.Description = "Jerry needs some drinks."
err = ldapClient.Update(foo)
if err != nil {
t.Fatal(err)
@@ -85,7 +89,7 @@ func TestSimpleLdap(t *testing.T) {
t.Fatal(err)
}
err = ldapClient.Authenticate(foo.Name, foo.Password)
err = ldapClient.Authenticate(foo.Name, foo.Spec.EncryptedPassword)
if err != nil {
t.Fatalf("should pass but got an error %v", err)
}

View File

@@ -1,22 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

View File

@@ -1,10 +0,0 @@
language: go
go:
- 1.7
- 1.x
- tip
before_install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
script:
- $HOME/gopath/bin/goveralls -service=travis-ci

View File

@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Cenk Altı
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,30 +0,0 @@
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls]
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
[Exponential backoff][exponential backoff wiki]
is an algorithm that uses feedback to multiplicatively decrease the rate of some process,
in order to gradually find an acceptable rate.
The retries exponentially increase and stop increasing when a certain threshold is met.
## Usage
See https://godoc.org/github.com/cenkalti/backoff#pkg-examples
## Contributing
* I would like to keep this library as small as possible.
* Please don't send a PR without opening an issue and discussing it first.
* If proposed change is not a common use case, I will probably not accept it.
[godoc]: https://godoc.org/github.com/cenkalti/backoff
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
[travis]: https://travis-ci.org/cenkalti/backoff
[travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master
[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master
[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master
[google-http-java-client]: https://github.com/google/google-http-java-client/blob/da1aa993e90285ec18579f1553339b00e19b3ab5/google-http-client/src/main/java/com/google/api/client/util/ExponentialBackOff.java
[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff
[advanced example]: https://godoc.org/github.com/cenkalti/backoff#example_

View File

@@ -1,66 +0,0 @@
// Package backoff implements backoff algorithms for retrying operations.
//
// Use Retry function for retrying operations that may fail.
// If Retry does not meet your needs,
// copy/paste the function into your project and modify as you wish.
//
// There is also Ticker type similar to time.Ticker.
// You can use it if you need to work with channels.
//
// See Examples section below for usage examples.
package backoff
import "time"
// BackOff is a backoff policy for retrying an operation.
type BackOff interface {
// NextBackOff returns the duration to wait before retrying the operation,
// or backoff. Stop to indicate that no more retries should be made.
//
// Example usage:
//
// duration := backoff.NextBackOff();
// if (duration == backoff.Stop) {
// // Do not retry operation.
// } else {
// // Sleep for duration and retry operation.
// }
//
NextBackOff() time.Duration
// Reset to initial state.
Reset()
}
// Stop indicates that no more retries should be made for use in NextBackOff().
const Stop time.Duration = -1
// ZeroBackOff is a fixed backoff policy whose backoff time is always zero,
// meaning that the operation is retried immediately without waiting, indefinitely.
type ZeroBackOff struct{}
func (b *ZeroBackOff) Reset() {}
func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 }
// StopBackOff is a fixed backoff policy that always returns backoff.Stop for
// NextBackOff(), meaning that the operation should never be retried.
type StopBackOff struct{}
func (b *StopBackOff) Reset() {}
func (b *StopBackOff) NextBackOff() time.Duration { return Stop }
// ConstantBackOff is a backoff policy that always returns the same backoff delay.
// This is in contrast to an exponential backoff policy,
// which returns a delay that grows longer as you call NextBackOff() over and over again.
type ConstantBackOff struct {
Interval time.Duration
}
func (b *ConstantBackOff) Reset() {}
func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval }
func NewConstantBackOff(d time.Duration) *ConstantBackOff {
return &ConstantBackOff{Interval: d}
}

View File

@@ -1,63 +0,0 @@
package backoff
import (
"context"
"time"
)
// BackOffContext is a backoff policy that stops retrying after the context
// is canceled.
type BackOffContext interface {
BackOff
Context() context.Context
}
type backOffContext struct {
BackOff
ctx context.Context
}
// WithContext returns a BackOffContext with context ctx
//
// ctx must not be nil
func WithContext(b BackOff, ctx context.Context) BackOffContext {
if ctx == nil {
panic("nil context")
}
if b, ok := b.(*backOffContext); ok {
return &backOffContext{
BackOff: b.BackOff,
ctx: ctx,
}
}
return &backOffContext{
BackOff: b,
ctx: ctx,
}
}
func ensureContext(b BackOff) BackOffContext {
if cb, ok := b.(BackOffContext); ok {
return cb
}
return WithContext(b, context.Background())
}
func (b *backOffContext) Context() context.Context {
return b.ctx
}
func (b *backOffContext) NextBackOff() time.Duration {
select {
case <-b.ctx.Done():
return Stop
default:
}
next := b.BackOff.NextBackOff()
if deadline, ok := b.ctx.Deadline(); ok && deadline.Sub(time.Now()) < next {
return Stop
}
return next
}

View File

@@ -1,153 +0,0 @@
package backoff
import (
"math/rand"
"time"
)
/*
ExponentialBackOff is a backoff implementation that increases the backoff
period for each retry attempt using a randomization function that grows exponentially.
NextBackOff() is calculated using the following formula:
randomized interval =
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
In other words NextBackOff() will range between the randomization factor
percentage below and above the retry interval.
For example, given the following parameters:
RetryInterval = 2
RandomizationFactor = 0.5
Multiplier = 2
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
multiplied by the exponential, that is, between 2 and 6 seconds.
Note: MaxInterval caps the RetryInterval and not the randomized interval.
If the time elapsed since an ExponentialBackOff instance is created goes past the
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
The elapsed time can be reset by calling Reset().
Example: Given the following default arguments, for 10 tries the sequence will be,
and assuming we go over the MaxElapsedTime on the 10th try:
Request # RetryInterval (seconds) Randomized Interval (seconds)
1 0.5 [0.25, 0.75]
2 0.75 [0.375, 1.125]
3 1.125 [0.562, 1.687]
4 1.687 [0.8435, 2.53]
5 2.53 [1.265, 3.795]
6 3.795 [1.897, 5.692]
7 5.692 [2.846, 8.538]
8 8.538 [4.269, 12.807]
9 12.807 [6.403, 19.210]
10 19.210 backoff.Stop
Note: Implementation is not thread-safe.
*/
type ExponentialBackOff struct {
InitialInterval time.Duration
RandomizationFactor float64
Multiplier float64
MaxInterval time.Duration
// After MaxElapsedTime the ExponentialBackOff stops.
// It never stops if MaxElapsedTime == 0.
MaxElapsedTime time.Duration
Clock Clock
currentInterval time.Duration
startTime time.Time
}
// Clock is an interface that returns current time for BackOff.
type Clock interface {
Now() time.Time
}
// Default values for ExponentialBackOff.
const (
DefaultInitialInterval = 500 * time.Millisecond
DefaultRandomizationFactor = 0.5
DefaultMultiplier = 1.5
DefaultMaxInterval = 60 * time.Second
DefaultMaxElapsedTime = 15 * time.Minute
)
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
func NewExponentialBackOff() *ExponentialBackOff {
b := &ExponentialBackOff{
InitialInterval: DefaultInitialInterval,
RandomizationFactor: DefaultRandomizationFactor,
Multiplier: DefaultMultiplier,
MaxInterval: DefaultMaxInterval,
MaxElapsedTime: DefaultMaxElapsedTime,
Clock: SystemClock,
}
b.Reset()
return b
}
type systemClock struct{}
func (t systemClock) Now() time.Time {
return time.Now()
}
// SystemClock implements Clock interface that uses time.Now().
var SystemClock = systemClock{}
// Reset the interval back to the initial retry interval and restarts the timer.
func (b *ExponentialBackOff) Reset() {
b.currentInterval = b.InitialInterval
b.startTime = b.Clock.Now()
}
// NextBackOff calculates the next backoff interval using the formula:
// Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval)
func (b *ExponentialBackOff) NextBackOff() time.Duration {
// Make sure we have not gone over the maximum elapsed time.
if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime {
return Stop
}
defer b.incrementCurrentInterval()
return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
}
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
// is created and is reset when Reset() is called.
//
// The elapsed time is computed using time.Now().UnixNano(). It is
// safe to call even while the backoff policy is used by a running
// ticker.
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
return b.Clock.Now().Sub(b.startTime)
}
// Increments the current interval by multiplying it with the multiplier.
func (b *ExponentialBackOff) incrementCurrentInterval() {
// Check for overflow, if overflow is detected set the current interval to the max interval.
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
b.currentInterval = b.MaxInterval
} else {
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
}
}
// Returns a random value from the following interval:
// [randomizationFactor * currentInterval, randomizationFactor * currentInterval].
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
var delta = randomizationFactor * float64(currentInterval)
var minInterval = float64(currentInterval) - delta
var maxInterval = float64(currentInterval) + delta
// Get a random value from the range [minInterval, maxInterval].
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
// we want a 33% chance for selecting either 1, 2 or 3.
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
}

View File

@@ -1,82 +0,0 @@
package backoff
import "time"
// An Operation is executing by Retry() or RetryNotify().
// The operation will be retried using a backoff policy if it returns an error.
type Operation func() error
// Notify is a notify-on-error function. It receives an operation error and
// backoff delay if the operation failed (with an error).
//
// NOTE that if the backoff policy stated to stop retrying,
// the notify function isn't called.
type Notify func(error, time.Duration)
// Retry the operation o until it does not return error or BackOff stops.
// o is guaranteed to be run at least once.
//
// If o returns a *PermanentError, the operation is not retried, and the
// wrapped error is returned.
//
// Retry sleeps the goroutine for the duration returned by BackOff after a
// failed operation returns.
func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) }
// RetryNotify calls notify function with the error and wait duration
// for each failed attempt before sleep.
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
var err error
var next time.Duration
var t *time.Timer
cb := ensureContext(b)
b.Reset()
for {
if err = operation(); err == nil {
return nil
}
if permanent, ok := err.(*PermanentError); ok {
return permanent.Err
}
if next = cb.NextBackOff(); next == Stop {
return err
}
if notify != nil {
notify(err, next)
}
if t == nil {
t = time.NewTimer(next)
defer t.Stop()
} else {
t.Reset(next)
}
select {
case <-cb.Context().Done():
return err
case <-t.C:
}
}
}
// PermanentError signals that the operation should not be retried.
type PermanentError struct {
Err error
}
func (e *PermanentError) Error() string {
return e.Err.Error()
}
// Permanent wraps the given err in a *PermanentError.
func Permanent(err error) *PermanentError {
return &PermanentError{
Err: err,
}
}

View File

@@ -1,82 +0,0 @@
package backoff
import (
"sync"
"time"
)
// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff.
//
// Ticks will continue to arrive when the previous operation is still running,
// so operations that take a while to fail could run in quick succession.
type Ticker struct {
C <-chan time.Time
c chan time.Time
b BackOffContext
stop chan struct{}
stopOnce sync.Once
}
// NewTicker returns a new Ticker containing a channel that will send
// the time at times specified by the BackOff argument. Ticker is
// guaranteed to tick at least once. The channel is closed when Stop
// method is called or BackOff stops. It is not safe to manipulate the
// provided backoff policy (notably calling NextBackOff or Reset)
// while the ticker is running.
func NewTicker(b BackOff) *Ticker {
c := make(chan time.Time)
t := &Ticker{
C: c,
c: c,
b: ensureContext(b),
stop: make(chan struct{}),
}
t.b.Reset()
go t.run()
return t
}
// Stop turns off a ticker. After Stop, no more ticks will be sent.
func (t *Ticker) Stop() {
t.stopOnce.Do(func() { close(t.stop) })
}
func (t *Ticker) run() {
c := t.c
defer close(c)
// Ticker is guaranteed to tick at least once.
afterC := t.send(time.Now())
for {
if afterC == nil {
return
}
select {
case tick := <-afterC:
afterC = t.send(tick)
case <-t.stop:
t.c = nil // Prevent future ticks from being sent to the channel.
return
case <-t.b.Context().Done():
return
}
}
}
func (t *Ticker) send(tick time.Time) <-chan time.Time {
select {
case t.c <- tick:
case <-t.stop:
return nil
}
next := t.b.NextBackOff()
if next == Stop {
t.Stop()
return nil
}
return time.After(next)
}

View File

@@ -1,35 +0,0 @@
package backoff
import "time"
/*
WithMaxRetries creates a wrapper around another BackOff, which will
return Stop if NextBackOff() has been called too many times since
the last time Reset() was called
Note: Implementation is not thread-safe.
*/
func WithMaxRetries(b BackOff, max uint64) BackOff {
return &backOffTries{delegate: b, maxTries: max}
}
type backOffTries struct {
delegate BackOff
maxTries uint64
numTries uint64
}
func (b *backOffTries) NextBackOff() time.Duration {
if b.maxTries > 0 {
if b.maxTries <= b.numTries {
return Stop
}
b.numTries++
}
return b.delegate.NextBackOff()
}
func (b *backOffTries) Reset() {
b.numTries = 0
b.delegate.Reset()
}

View File

@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 cheekybits
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,2 +0,0 @@
// Package generic contains the generic marker types.
package generic

View File

@@ -1,13 +0,0 @@
package generic
// Type is the placeholder type that indicates a generic value.
// When genny is executed, variables of this type will be replaced with
// references to the specific types.
// var GenericType generic.Type
type Type interface{}
// Number is the placehoder type that indiccates a generic numerical value.
// When genny is executed, variables of this type will be replaced with
// references to the specific types.
// var GenericType generic.Number
type Number float64

View File

@@ -1,21 +0,0 @@
sudo: false
language: go
go:
- 1.3.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- master
matrix:
allow_failures:
- go: master
fast_finish: true
install:
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d -s .)
- go tool vet .
- go test -v -race ./...

View File

@@ -1,21 +0,0 @@
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
<http://www.opensource.org/licenses/mit-license.php>

View File

@@ -1,124 +0,0 @@
# Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize)
Just a few functions for helping humanize times and sizes.
`go get` it as `github.com/dustin/go-humanize`, import it as
`"github.com/dustin/go-humanize"`, use it as `humanize`.
See [godoc](https://godoc.org/github.com/dustin/go-humanize) for
complete documentation.
## Sizes
This lets you take numbers like `82854982` and convert them to useful
strings like, `83 MB` or `79 MiB` (whichever you prefer).
Example:
```go
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
```
## Times
This lets you take a `time.Time` and spit it out in relative terms.
For example, `12 seconds ago` or `3 days from now`.
Example:
```go
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
```
Thanks to Kyle Lemons for the time implementation from an IRC
conversation one day. It's pretty neat.
## Ordinals
From a [mailing list discussion][odisc] where a user wanted to be able
to label ordinals.
0 -> 0th
1 -> 1st
2 -> 2nd
3 -> 3rd
4 -> 4th
[...]
Example:
```go
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
```
## Commas
Want to shove commas into numbers? Be my guest.
0 -> 0
100 -> 100
1000 -> 1,000
1000000000 -> 1,000,000,000
-100000 -> -100,000
Example:
```go
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
```
## Ftoa
Nicer float64 formatter that removes trailing zeros.
```go
fmt.Printf("%f", 2.24) // 2.240000
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
fmt.Printf("%f", 2.0) // 2.000000
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
```
## SI notation
Format numbers with [SI notation][sinotation].
Example:
```go
humanize.SI(0.00000000223, "M") // 2.23 nM
```
## English-specific functions
The following functions are in the `humanize/english` subpackage.
### Plurals
Simple English pluralization
```go
english.PluralWord(1, "object", "") // object
english.PluralWord(42, "object", "") // objects
english.PluralWord(2, "bus", "") // buses
english.PluralWord(99, "locus", "loci") // loci
english.Plural(1, "object", "") // 1 object
english.Plural(42, "object", "") // 42 objects
english.Plural(2, "bus", "") // 2 buses
english.Plural(99, "locus", "loci") // 99 loci
```
### Word series
Format comma-separated words lists with conjuctions:
```go
english.WordSeries([]string{"foo"}, "and") // foo
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
```
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix

View File

@@ -1,31 +0,0 @@
package humanize
import (
"math/big"
)
// order of magnitude (to a max order)
func oomm(n, b *big.Int, maxmag int) (float64, int) {
mag := 0
m := &big.Int{}
for n.Cmp(b) >= 0 {
n.DivMod(n, b, m)
mag++
if mag == maxmag && maxmag >= 0 {
break
}
}
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
}
// total order of magnitude
// (same as above, but with no upper limit)
func oom(n, b *big.Int) (float64, int) {
mag := 0
m := &big.Int{}
for n.Cmp(b) >= 0 {
n.DivMod(n, b, m)
mag++
}
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
}

View File

@@ -1,173 +0,0 @@
package humanize
import (
"fmt"
"math/big"
"strings"
"unicode"
)
var (
bigIECExp = big.NewInt(1024)
// BigByte is one byte in bit.Ints
BigByte = big.NewInt(1)
// BigKiByte is 1,024 bytes in bit.Ints
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
// BigMiByte is 1,024 k bytes in bit.Ints
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
// BigGiByte is 1,024 m bytes in bit.Ints
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
// BigTiByte is 1,024 g bytes in bit.Ints
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
// BigPiByte is 1,024 t bytes in bit.Ints
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
// BigEiByte is 1,024 p bytes in bit.Ints
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
// BigZiByte is 1,024 e bytes in bit.Ints
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
// BigYiByte is 1,024 z bytes in bit.Ints
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
)
var (
bigSIExp = big.NewInt(1000)
// BigSIByte is one SI byte in big.Ints
BigSIByte = big.NewInt(1)
// BigKByte is 1,000 SI bytes in big.Ints
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
// BigMByte is 1,000 SI k bytes in big.Ints
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
// BigGByte is 1,000 SI m bytes in big.Ints
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
// BigTByte is 1,000 SI g bytes in big.Ints
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
// BigPByte is 1,000 SI t bytes in big.Ints
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
// BigEByte is 1,000 SI p bytes in big.Ints
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
// BigZByte is 1,000 SI e bytes in big.Ints
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
// BigYByte is 1,000 SI z bytes in big.Ints
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
)
var bigBytesSizeTable = map[string]*big.Int{
"b": BigByte,
"kib": BigKiByte,
"kb": BigKByte,
"mib": BigMiByte,
"mb": BigMByte,
"gib": BigGiByte,
"gb": BigGByte,
"tib": BigTiByte,
"tb": BigTByte,
"pib": BigPiByte,
"pb": BigPByte,
"eib": BigEiByte,
"eb": BigEByte,
"zib": BigZiByte,
"zb": BigZByte,
"yib": BigYiByte,
"yb": BigYByte,
// Without suffix
"": BigByte,
"ki": BigKiByte,
"k": BigKByte,
"mi": BigMiByte,
"m": BigMByte,
"gi": BigGiByte,
"g": BigGByte,
"ti": BigTiByte,
"t": BigTByte,
"pi": BigPiByte,
"p": BigPByte,
"ei": BigEiByte,
"e": BigEByte,
"z": BigZByte,
"zi": BigZiByte,
"y": BigYByte,
"yi": BigYiByte,
}
var ten = big.NewInt(10)
func humanateBigBytes(s, base *big.Int, sizes []string) string {
if s.Cmp(ten) < 0 {
return fmt.Sprintf("%d B", s)
}
c := (&big.Int{}).Set(s)
val, mag := oomm(c, base, len(sizes)-1)
suffix := sizes[mag]
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
// BigBytes produces a human readable representation of an SI size.
//
// See also: ParseBigBytes.
//
// BigBytes(82854982) -> 83 MB
func BigBytes(s *big.Int) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
return humanateBigBytes(s, bigSIExp, sizes)
}
// BigIBytes produces a human readable representation of an IEC size.
//
// See also: ParseBigBytes.
//
// BigIBytes(82854982) -> 79 MiB
func BigIBytes(s *big.Int) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
return humanateBigBytes(s, bigIECExp, sizes)
}
// ParseBigBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// See also: BigBytes, BigIBytes.
//
// ParseBigBytes("42 MB") -> 42000000, nil
// ParseBigBytes("42 mib") -> 44040192, nil
func ParseBigBytes(s string) (*big.Int, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
val := &big.Rat{}
_, err := fmt.Sscanf(num, "%f", val)
if err != nil {
return nil, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := bigBytesSizeTable[extra]; ok {
mv := (&big.Rat{}).SetInt(m)
val.Mul(val, mv)
rv := &big.Int{}
rv.Div(val.Num(), val.Denom())
return rv, nil
}
return nil, fmt.Errorf("unhandled size name: %v", extra)
}

View File

@@ -1,143 +0,0 @@
package humanize
import (
"fmt"
"math"
"strconv"
"strings"
"unicode"
)
// IEC Sizes.
// kibis of bits
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
// SI Sizes.
const (
IByte = 1
KByte = IByte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var bytesSizeTable = map[string]uint64{
"b": Byte,
"kib": KiByte,
"kb": KByte,
"mib": MiByte,
"mb": MByte,
"gib": GiByte,
"gb": GByte,
"tib": TiByte,
"tb": TByte,
"pib": PiByte,
"pb": PByte,
"eib": EiByte,
"eb": EByte,
// Without suffix
"": Byte,
"ki": KiByte,
"k": KByte,
"mi": MiByte,
"m": MByte,
"gi": GiByte,
"g": GByte,
"ti": TiByte,
"t": TByte,
"pi": PiByte,
"p": PByte,
"ei": EiByte,
"e": EByte,
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
// Bytes produces a human readable representation of an SI size.
//
// See also: ParseBytes.
//
// Bytes(82854982) -> 83 MB
func Bytes(s uint64) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
return humanateBytes(s, 1000, sizes)
}
// IBytes produces a human readable representation of an IEC size.
//
// See also: ParseBytes.
//
// IBytes(82854982) -> 79 MiB
func IBytes(s uint64) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
return humanateBytes(s, 1024, sizes)
}
// ParseBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// See Also: Bytes, IBytes.
//
// ParseBytes("42 MB") -> 42000000, nil
// ParseBytes("42 mib") -> 44040192, nil
func ParseBytes(s string) (uint64, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
f, err := strconv.ParseFloat(num, 64)
if err != nil {
return 0, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := bytesSizeTable[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
return 0, fmt.Errorf("unhandled size name: %v", extra)
}

View File

@@ -1,116 +0,0 @@
package humanize
import (
"bytes"
"math"
"math/big"
"strconv"
"strings"
)
// Comma produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Comma(834142) -> 834,142
func Comma(v int64) string {
sign := ""
// Min int64 can't be negated to a usable value, so it has to be special cased.
if v == math.MinInt64 {
return "-9,223,372,036,854,775,808"
}
if v < 0 {
sign = "-"
v = 0 - v
}
parts := []string{"", "", "", "", "", "", ""}
j := len(parts) - 1
for v > 999 {
parts[j] = strconv.FormatInt(v%1000, 10)
switch len(parts[j]) {
case 2:
parts[j] = "0" + parts[j]
case 1:
parts[j] = "00" + parts[j]
}
v = v / 1000
j--
}
parts[j] = strconv.Itoa(int(v))
return sign + strings.Join(parts[j:], ",")
}
// Commaf produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Commaf(834142.32) -> 834,142.32
func Commaf(v float64) string {
buf := &bytes.Buffer{}
if v < 0 {
buf.Write([]byte{'-'})
v = 0 - v
}
comma := []byte{','}
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
pos := 0
if len(parts[0])%3 != 0 {
pos += len(parts[0]) % 3
buf.WriteString(parts[0][:pos])
buf.Write(comma)
}
for ; pos < len(parts[0]); pos += 3 {
buf.WriteString(parts[0][pos : pos+3])
buf.Write(comma)
}
buf.Truncate(buf.Len() - 1)
if len(parts) > 1 {
buf.Write([]byte{'.'})
buf.WriteString(parts[1])
}
return buf.String()
}
// CommafWithDigits works like the Commaf but limits the resulting
// string to the given number of decimal places.
//
// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
func CommafWithDigits(f float64, decimals int) string {
return stripTrailingDigits(Commaf(f), decimals)
}
// BigComma produces a string form of the given big.Int in base 10
// with commas after every three orders of magnitude.
func BigComma(b *big.Int) string {
sign := ""
if b.Sign() < 0 {
sign = "-"
b.Abs(b)
}
athousand := big.NewInt(1000)
c := (&big.Int{}).Set(b)
_, m := oom(c, athousand)
parts := make([]string, m+1)
j := len(parts) - 1
mod := &big.Int{}
for b.Cmp(athousand) >= 0 {
b.DivMod(b, athousand, mod)
parts[j] = strconv.FormatInt(mod.Int64(), 10)
switch len(parts[j]) {
case 2:
parts[j] = "0" + parts[j]
case 1:
parts[j] = "00" + parts[j]
}
j--
}
parts[j] = strconv.Itoa(int(b.Int64()))
return sign + strings.Join(parts[j:], ",")
}

View File

@@ -1,40 +0,0 @@
// +build go1.6
package humanize
import (
"bytes"
"math/big"
"strings"
)
// BigCommaf produces a string form of the given big.Float in base 10
// with commas after every three orders of magnitude.
func BigCommaf(v *big.Float) string {
buf := &bytes.Buffer{}
if v.Sign() < 0 {
buf.Write([]byte{'-'})
v.Abs(v)
}
comma := []byte{','}
parts := strings.Split(v.Text('f', -1), ".")
pos := 0
if len(parts[0])%3 != 0 {
pos += len(parts[0]) % 3
buf.WriteString(parts[0][:pos])
buf.Write(comma)
}
for ; pos < len(parts[0]); pos += 3 {
buf.WriteString(parts[0][pos : pos+3])
buf.Write(comma)
}
buf.Truncate(buf.Len() - 1)
if len(parts) > 1 {
buf.Write([]byte{'.'})
buf.WriteString(parts[1])
}
return buf.String()
}

View File

@@ -1,46 +0,0 @@
package humanize
import (
"strconv"
"strings"
)
func stripTrailingZeros(s string) string {
offset := len(s) - 1
for offset > 0 {
if s[offset] == '.' {
offset--
break
}
if s[offset] != '0' {
break
}
offset--
}
return s[:offset+1]
}
func stripTrailingDigits(s string, digits int) string {
if i := strings.Index(s, "."); i >= 0 {
if digits <= 0 {
return s[:i]
}
i++
if i+digits >= len(s) {
return s
}
return s[:i+digits]
}
return s
}
// Ftoa converts a float to a string with no trailing zeros.
func Ftoa(num float64) string {
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
}
// FtoaWithDigits converts a float to a string but limits the resulting string
// to the given number of decimal places, and no trailing zeros.
func FtoaWithDigits(num float64, digits int) string {
return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
}

View File

@@ -1,8 +0,0 @@
/*
Package humanize converts boring ugly numbers to human-friendly strings and back.
Durations can be turned into strings such as "3 days ago", numbers
representing sizes like 82854982 into useful strings like, "83 MB" or
"79 MiB" (whichever you prefer).
*/
package humanize

View File

@@ -1,192 +0,0 @@
package humanize
/*
Slightly adapted from the source to fit go-humanize.
Author: https://github.com/gorhill
Source: https://gist.github.com/gorhill/5285193
*/
import (
"math"
"strconv"
)
var (
renderFloatPrecisionMultipliers = [...]float64{
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
}
renderFloatPrecisionRounders = [...]float64{
0.5,
0.05,
0.005,
0.0005,
0.00005,
0.000005,
0.0000005,
0.00000005,
0.000000005,
0.0000000005,
}
)
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
// * thousands separator
// * decimal separator
// * decimal precision
//
// Usage: s := RenderFloat(format, n)
// The format parameter tells how to render the number n.
//
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
//
// Examples of format strings, given n = 12345.6789:
// "#,###.##" => "12,345.67"
// "#,###." => "12,345"
// "#,###" => "12345,678"
// "#\u202F###,##" => "12345,68"
// "#.###,###### => 12.345,678900
// "" (aka default format) => 12,345.67
//
// The highest precision allowed is 9 digits after the decimal symbol.
// There is also a version for integer number, FormatInteger(),
// which is convenient for calls within template.
func FormatFloat(format string, n float64) string {
// Special cases:
// NaN = "NaN"
// +Inf = "+Infinity"
// -Inf = "-Infinity"
if math.IsNaN(n) {
return "NaN"
}
if n > math.MaxFloat64 {
return "Infinity"
}
if n < -math.MaxFloat64 {
return "-Infinity"
}
// default format
precision := 2
decimalStr := "."
thousandStr := ","
positiveStr := ""
negativeStr := "-"
if len(format) > 0 {
format := []rune(format)
// If there is an explicit format directive,
// then default values are these:
precision = 9
thousandStr = ""
// collect indices of meaningful formatting directives
formatIndx := []int{}
for i, char := range format {
if char != '#' && char != '0' {
formatIndx = append(formatIndx, i)
}
}
if len(formatIndx) > 0 {
// Directive at index 0:
// Must be a '+'
// Raise an error if not the case
// index: 0123456789
// +0.000,000
// +000,000.0
// +0000.00
// +0000
if formatIndx[0] == 0 {
if format[formatIndx[0]] != '+' {
panic("RenderFloat(): invalid positive sign directive")
}
positiveStr = "+"
formatIndx = formatIndx[1:]
}
// Two directives:
// First is thousands separator
// Raise an error if not followed by 3-digit
// 0123456789
// 0.000,000
// 000,000.00
if len(formatIndx) == 2 {
if (formatIndx[1] - formatIndx[0]) != 4 {
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
}
thousandStr = string(format[formatIndx[0]])
formatIndx = formatIndx[1:]
}
// One directive:
// Directive is decimal separator
// The number of digit-specifier following the separator indicates wanted precision
// 0123456789
// 0.00
// 000,0000
if len(formatIndx) == 1 {
decimalStr = string(format[formatIndx[0]])
precision = len(format) - formatIndx[0] - 1
}
}
}
// generate sign part
var signStr string
if n >= 0.000000001 {
signStr = positiveStr
} else if n <= -0.000000001 {
signStr = negativeStr
n = -n
} else {
signStr = ""
n = 0.0
}
// split number into integer and fractional parts
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
// generate integer part string
intStr := strconv.FormatInt(int64(intf), 10)
// add thousand separator if required
if len(thousandStr) > 0 {
for i := len(intStr); i > 3; {
i -= 3
intStr = intStr[:i] + thousandStr + intStr[i:]
}
}
// no fractional part, we can leave now
if precision == 0 {
return signStr + intStr
}
// generate fractional part
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
// may need padding
if len(fracStr) < precision {
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
}
return signStr + intStr + decimalStr + fracStr
}
// FormatInteger produces a formatted number as string.
// See FormatFloat.
func FormatInteger(format string, n int) string {
return FormatFloat(format, float64(n))
}

View File

@@ -1,25 +0,0 @@
package humanize
import "strconv"
// Ordinal gives you the input number in a rank/ordinal format.
//
// Ordinal(3) -> 3rd
func Ordinal(x int) string {
suffix := "th"
switch x % 10 {
case 1:
if x%100 != 11 {
suffix = "st"
}
case 2:
if x%100 != 12 {
suffix = "nd"
}
case 3:
if x%100 != 13 {
suffix = "rd"
}
}
return strconv.Itoa(x) + suffix
}

View File

@@ -1,123 +0,0 @@
package humanize
import (
"errors"
"math"
"regexp"
"strconv"
)
var siPrefixTable = map[float64]string{
-24: "y", // yocto
-21: "z", // zepto
-18: "a", // atto
-15: "f", // femto
-12: "p", // pico
-9: "n", // nano
-6: "µ", // micro
-3: "m", // milli
0: "",
3: "k", // kilo
6: "M", // mega
9: "G", // giga
12: "T", // tera
15: "P", // peta
18: "E", // exa
21: "Z", // zetta
24: "Y", // yotta
}
var revSIPrefixTable = revfmap(siPrefixTable)
// revfmap reverses the map and precomputes the power multiplier
func revfmap(in map[float64]string) map[string]float64 {
rv := map[string]float64{}
for k, v := range in {
rv[v] = math.Pow(10, k)
}
return rv
}
var riParseRegex *regexp.Regexp
func init() {
ri := `^([\-0-9.]+)\s?([`
for _, v := range siPrefixTable {
ri += v
}
ri += `]?)(.*)`
riParseRegex = regexp.MustCompile(ri)
}
// ComputeSI finds the most appropriate SI prefix for the given number
// and returns the prefix along with the value adjusted to be within
// that prefix.
//
// See also: SI, ParseSI.
//
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
func ComputeSI(input float64) (float64, string) {
if input == 0 {
return 0, ""
}
mag := math.Abs(input)
exponent := math.Floor(logn(mag, 10))
exponent = math.Floor(exponent/3) * 3
value := mag / math.Pow(10, exponent)
// Handle special case where value is exactly 1000.0
// Should return 1 M instead of 1000 k
if value == 1000.0 {
exponent += 3
value = mag / math.Pow(10, exponent)
}
value = math.Copysign(value, input)
prefix := siPrefixTable[exponent]
return value, prefix
}
// SI returns a string with default formatting.
//
// SI uses Ftoa to format float value, removing trailing zeros.
//
// See also: ComputeSI, ParseSI.
//
// e.g. SI(1000000, "B") -> 1 MB
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
func SI(input float64, unit string) string {
value, prefix := ComputeSI(input)
return Ftoa(value) + " " + prefix + unit
}
// SIWithDigits works like SI but limits the resulting string to the
// given number of decimal places.
//
// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
func SIWithDigits(input float64, decimals int, unit string) string {
value, prefix := ComputeSI(input)
return FtoaWithDigits(value, decimals) + " " + prefix + unit
}
var errInvalid = errors.New("invalid input")
// ParseSI parses an SI string back into the number and unit.
//
// See also: SI, ComputeSI.
//
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
func ParseSI(input string) (float64, string, error) {
found := riParseRegex.FindStringSubmatch(input)
if len(found) != 4 {
return 0, "", errInvalid
}
mag := revSIPrefixTable[found[2]]
unit := found[3]
base, err := strconv.ParseFloat(found[1], 64)
return base * mag, unit, err
}

Some files were not shown because too many files have changed in this diff Show More