1
0
mirror of https://github.com/rancher/norman.git synced 2025-09-05 01:00:36 +00:00

Update vendor

This commit is contained in:
Darren Shepherd
2018-10-30 10:46:47 -07:00
parent 92a6f9ebee
commit 418b1d3d37
153 changed files with 482 additions and 12431 deletions

View File

@@ -1,8 +1,8 @@
# package
github.com/rancher/norman
k8s.io/kubernetes v1.12.1-lite7 https://github.com/ibuildthecloud/k3s.git transitive=true,staging=true
github.com/maruel/panicparse c0182c169410cfa80c7e8f046dad208eaef91338
bitbucket.org/ww/goautoneg a547fc61f48d567d5b4ec6f8aee5573d8efce11d https://github.com/rancher/goautoneg.git
golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5
github.com/gorilla/mux v1.6.1
k8s.io/kubernetes v1.12.2-lite1 https://github.com/ibuildthecloud/k3s.git transitive=true,staging=true
github.com/maruel/panicparse c0182c169410cfa80c7e8f046dad208eaef91338
bitbucket.org/ww/goautoneg a547fc61f48d567d5b4ec6f8aee5573d8efce11d https://github.com/rancher/goautoneg.git
golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5
github.com/gorilla/mux v1.6.1

View File

@@ -8,8 +8,8 @@ import (
"sync/atomic"
"time"
"github.com/ibuildthecloud/kvsql/pkg/broadcast"
"github.com/pkg/errors"
"github.com/rancher/norman/pkg/broadcast"
"github.com/sirupsen/logrus"
utiltrace "k8s.io/apiserver/pkg/util/trace"
)

View File

@@ -1,5 +0,0 @@
./bin
./.dapper
./dist
./.trash-cache
./.idea

View File

@@ -1,9 +0,0 @@
---
pipeline:
build:
privileged: true
image: rancher/dapper:1.11.2
volumes:
- /var/run/docker.sock:/var/run/docker.sock
commands:
- dapper ci

View File

@@ -1,7 +0,0 @@
/.idea
/.dapper
/bin
/dist
*.swp
/.trash-cache
/trash.lock

View File

@@ -1,16 +0,0 @@
FROM golang:1.11-alpine
RUN apk -U add bash git gcc musl-dev docker
RUN go get -d golang.org/x/lint/golint && \
git -C /go/src/golang.org/x/lint/golint checkout -b current 06c8688daad7faa9da5a0c2f163a3d14aac986ca && \
go install golang.org/x/lint/golint && \
rm -rf /go/src /go/pkg
ENV DAPPER_SOURCE /go/src/github.com/rancher/norman/
ENV DAPPER_OUTPUT ./bin ./dist
ENV DAPPER_DOCKER_SOCKET true
ENV HOME ${DAPPER_SOURCE}
WORKDIR ${DAPPER_SOURCE}
ENTRYPOINT ["./scripts/entry"]
CMD ["ci"]

View File

@@ -1,177 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -1,15 +0,0 @@
TARGETS := $(shell ls scripts)
.dapper:
@echo Downloading dapper
@curl -sL https://releases.rancher.com/dapper/latest/dapper-`uname -s`-`uname -m` > .dapper.tmp
@@chmod +x .dapper.tmp
@./.dapper.tmp -v
@mv .dapper.tmp .dapper
$(TARGETS): .dapper
./.dapper $@
.DEFAULT_GOAL := ci
.PHONY: $(TARGETS)

View File

@@ -1,78 +0,0 @@
Norman
========
An API framework for Building [Rancher Style APIs](https://github.com/rancher/api-spec/) backed by K8s CustomResources.
## Building
`make`
## Example
Refer to `examples/`
```go
package main
import (
"context"
"fmt"
"net/http"
"os"
"github.com/rancher/norman/generator"
"github.com/rancher/norman/server"
"github.com/rancher/norman/types"
)
type Foo struct {
types.Resource
Name string `json:"name"`
Foo string `json:"foo"`
SubThing Baz `json:"subThing"`
}
type Baz struct {
Name string `json:"name"`
}
var (
version = types.APIVersion{
Version: "v1",
Group: "io.cattle.core.example",
Path: "/example/v1",
}
Schemas = types.NewSchemas()
)
func main() {
if _, err := Schemas.Import(&version, Foo{}); err != nil {
panic(err)
}
server, err := server.NewAPIServer(context.Background(), os.Getenv("KUBECONFIG"), Schemas)
if err != nil {
panic(err)
}
fmt.Println("Listening on 0.0.0.0:1234")
http.ListenAndServe("0.0.0.0:1234", server)
}
```
## License
Copyright (c) 2014-2017 [Rancher Labs, Inc.](http://rancher.com)
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](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.

View File

@@ -1,87 +0,0 @@
package access
import (
"fmt"
"github.com/rancher/norman/parse/builder"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
)
func Create(context *types.APIContext, version *types.APIVersion, typeName string, data map[string]interface{}, into interface{}) error {
schema := context.Schemas.Schema(version, typeName)
if schema == nil {
return fmt.Errorf("failed to find schema " + typeName)
}
item, err := schema.Store.Create(context, schema, data)
if err != nil {
return err
}
b := builder.NewBuilder(context)
b.Version = version
item, err = b.Construct(schema, item, builder.List)
if err != nil {
return err
}
if into == nil {
return nil
}
return convert.ToObj(item, into)
}
func ByID(context *types.APIContext, version *types.APIVersion, typeName string, id string, into interface{}) error {
schema := context.Schemas.Schema(version, typeName)
if schema == nil {
return fmt.Errorf("failed to find schema " + typeName)
}
item, err := schema.Store.ByID(context, schema, id)
if err != nil {
return err
}
b := builder.NewBuilder(context)
b.Version = version
item, err = b.Construct(schema, item, builder.List)
if err != nil {
return err
}
if into == nil {
return nil
}
return convert.ToObj(item, into)
}
func List(context *types.APIContext, version *types.APIVersion, typeName string, opts *types.QueryOptions, into interface{}) error {
schema := context.Schemas.Schema(version, typeName)
if schema == nil {
return fmt.Errorf("failed to find schema " + typeName)
}
data, err := schema.Store.List(context, schema, opts)
if err != nil {
return err
}
b := builder.NewBuilder(context)
b.Version = version
var newData []map[string]interface{}
for _, item := range data {
item, err = b.Construct(schema, item, builder.List)
if err != nil {
return err
}
newData = append(newData, item)
}
return convert.ToObj(newData, into)
}

View File

@@ -1,81 +0,0 @@
package builtin
import (
"github.com/rancher/norman/store/empty"
"github.com/rancher/norman/types"
)
func APIRootFormatter(apiContext *types.APIContext, resource *types.RawResource) {
path, _ := resource.Values["path"].(string)
if path == "" {
return
}
delete(resource.Values, "path")
resource.Links["root"] = apiContext.URLBuilder.RelativeToRoot(path)
data, _ := resource.Values["apiVersion"].(map[string]interface{})
apiVersion := apiVersionFromMap(apiContext.Schemas, data)
resource.Links["self"] = apiContext.URLBuilder.Version(apiVersion)
for _, schema := range apiContext.Schemas.SchemasForVersion(apiVersion) {
addCollectionLink(apiContext, schema, resource.Links)
}
return
}
func addCollectionLink(apiContext *types.APIContext, schema *types.Schema, links map[string]string) {
collectionLink := getSchemaCollectionLink(apiContext, schema, nil)
if collectionLink != "" {
links[schema.PluralName] = collectionLink
}
}
type APIRootStore struct {
empty.Store
roots []string
}
func NewAPIRootStore(roots []string) types.Store {
return &APIRootStore{roots: roots}
}
func (a *APIRootStore) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
for _, version := range apiContext.Schemas.Versions() {
if version.Path == id {
return apiVersionToAPIRootMap(version), nil
}
}
return nil, nil
}
func (a *APIRootStore) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
var roots []map[string]interface{}
for _, version := range apiContext.Schemas.Versions() {
roots = append(roots, apiVersionToAPIRootMap(version))
}
for _, root := range a.roots {
roots = append(roots, map[string]interface{}{
"path": root,
})
}
return roots, nil
}
func apiVersionToAPIRootMap(version types.APIVersion) map[string]interface{} {
return map[string]interface{}{
"type": "/meta/schemas/apiRoot",
"apiVersion": map[string]interface{}{
"version": version.Version,
"group": version.Group,
"path": version.Path,
},
"path": version.Path,
}
}

View File

@@ -1,136 +0,0 @@
package builtin
import (
"net/http"
"github.com/rancher/norman/store/schema"
"github.com/rancher/norman/types"
)
var (
Version = types.APIVersion{
Group: "meta.cattle.io",
Version: "v1",
Path: "/meta",
}
Schema = types.Schema{
ID: "schema",
PluralName: "schemas",
Version: Version,
CollectionMethods: []string{"GET"},
ResourceMethods: []string{"GET"},
ResourceFields: map[string]types.Field{
"collectionActions": {Type: "map[json]"},
"collectionFields": {Type: "map[json]"},
"collectionFilters": {Type: "map[json]"},
"collectionMethods": {Type: "array[string]"},
"pluralName": {Type: "string"},
"resourceActions": {Type: "map[json]"},
"resourceFields": {Type: "map[json]"},
"resourceMethods": {Type: "array[string]"},
"version": {Type: "map[json]"},
},
Formatter: SchemaFormatter,
Store: schema.NewSchemaStore(),
}
Error = types.Schema{
ID: "error",
Version: Version,
ResourceMethods: []string{},
CollectionMethods: []string{},
ResourceFields: map[string]types.Field{
"code": {Type: "string"},
"detail": {Type: "string", Nullable: true},
"message": {Type: "string", Nullable: true},
"fieldName": {Type: "string", Nullable: true},
"status": {Type: "int"},
},
}
Collection = types.Schema{
ID: "collection",
Version: Version,
ResourceMethods: []string{},
CollectionMethods: []string{},
ResourceFields: map[string]types.Field{
"data": {Type: "array[json]"},
"pagination": {Type: "map[json]"},
"sort": {Type: "map[json]"},
"filters": {Type: "map[json]"},
},
}
APIRoot = types.Schema{
ID: "apiRoot",
Version: Version,
CollectionMethods: []string{"GET"},
ResourceMethods: []string{"GET"},
ResourceFields: map[string]types.Field{
"apiVersion": {Type: "map[json]"},
"path": {Type: "string"},
},
Formatter: APIRootFormatter,
Store: NewAPIRootStore(nil),
}
Schemas = types.NewSchemas().
AddSchema(Schema).
AddSchema(Error).
AddSchema(Collection).
AddSchema(APIRoot)
)
func apiVersionFromMap(schemas *types.Schemas, apiVersion map[string]interface{}) types.APIVersion {
path, _ := apiVersion["path"].(string)
version, _ := apiVersion["version"].(string)
group, _ := apiVersion["group"].(string)
apiVersionObj := types.APIVersion{
Path: path,
Version: version,
Group: group,
}
for _, testVersion := range schemas.Versions() {
if testVersion.Equals(&apiVersionObj) {
return testVersion
}
}
return apiVersionObj
}
func SchemaFormatter(apiContext *types.APIContext, resource *types.RawResource) {
data, _ := resource.Values["version"].(map[string]interface{})
apiVersion := apiVersionFromMap(apiContext.Schemas, data)
schema := apiContext.Schemas.Schema(&apiVersion, resource.ID)
if schema == nil {
return
}
collectionLink := getSchemaCollectionLink(apiContext, schema, &apiVersion)
if collectionLink != "" {
resource.Links["collection"] = collectionLink
}
resource.Links["self"] = apiContext.URLBuilder.SchemaLink(schema)
}
func getSchemaCollectionLink(apiContext *types.APIContext, schema *types.Schema, apiVersion *types.APIVersion) string {
if schema != nil && contains(schema.CollectionMethods, http.MethodGet) {
return apiContext.URLBuilder.Collection(schema, apiVersion)
}
return ""
}
func contains(list []string, needle string) bool {
for _, v := range list {
if v == needle {
return true
}
}
return false
}

View File

@@ -1,30 +0,0 @@
package handler
import (
"net/http"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
)
func CreateHandler(apiContext *types.APIContext, next types.RequestHandler) error {
var err error
data, err := ParseAndValidateBody(apiContext, true)
if err != nil {
return err
}
store := apiContext.Schema.Store
if store == nil {
return httperror.NewAPIError(httperror.NotFound, "no store found")
}
data, err = store.Create(apiContext, apiContext.Schema, data)
if err != nil {
return err
}
apiContext.WriteResponse(http.StatusCreated, data)
return nil
}

View File

@@ -1,27 +0,0 @@
package handler
import (
"net/http"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
)
func DeleteHandler(request *types.APIContext, next types.RequestHandler) error {
store := request.Schema.Store
if store == nil {
return httperror.NewAPIError(httperror.NotFound, "no store found")
}
obj, err := store.Delete(request, request.Schema, request.ID)
if err != nil {
return err
}
if obj == nil {
request.WriteResponse(http.StatusNoContent, nil)
} else {
request.WriteResponse(http.StatusOK, obj)
}
return nil
}

View File

@@ -1,43 +0,0 @@
package handler
import (
"net/http"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/parse"
"github.com/rancher/norman/types"
)
func ListHandler(request *types.APIContext, next types.RequestHandler) error {
var (
err error
data interface{}
)
store := request.Schema.Store
if store == nil {
return httperror.NewAPIError(httperror.NotFound, "no store found")
}
if request.ID == "" {
opts := parse.QueryOptions(request, request.Schema)
// Save the pagination on the context so it's not reset later
request.Pagination = opts.Pagination
data, err = store.List(request, request.Schema, &opts)
} else if request.Link == "" {
data, err = store.ByID(request, request.Schema, request.ID)
} else {
_, err = store.ByID(request, request.Schema, request.ID)
if err != nil {
return err
}
return request.Schema.LinkHandler(request, nil)
}
if err != nil {
return err
}
request.WriteResponse(http.StatusOK, data)
return nil
}

View File

@@ -1,121 +0,0 @@
package handler
import (
"sort"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
)
func QueryFilter(opts *types.QueryOptions, schema *types.Schema, data []map[string]interface{}) []map[string]interface{} {
return ApplyQueryOptions(opts, schema, data)
}
func ApplyQueryOptions(options *types.QueryOptions, schema *types.Schema, data []map[string]interface{}) []map[string]interface{} {
data = ApplyQueryConditions(options.Conditions, schema, data)
data = ApplySort(options.Sort, data)
return ApplyPagination(options.Pagination, data)
}
func ApplySort(sortOpts types.Sort, data []map[string]interface{}) []map[string]interface{} {
name := sortOpts.Name
if name == "" {
name = "id"
}
sort.Slice(data, func(i, j int) bool {
left, right := i, j
if sortOpts.Order == types.DESC {
left, right = j, i
}
return convert.ToString(data[left][name]) < convert.ToString(data[right][name])
})
return data
}
func ApplyQueryConditions(conditions []*types.QueryCondition, schema *types.Schema, data []map[string]interface{}) []map[string]interface{} {
var result []map[string]interface{}
outer:
for _, item := range data {
for _, condition := range conditions {
if !condition.Valid(schema, item) {
continue outer
}
}
result = append(result, item)
}
return result
}
func ApplyPagination(pagination *types.Pagination, data []map[string]interface{}) []map[string]interface{} {
if pagination == nil || pagination.Limit == nil {
return data
}
limit := *pagination.Limit
if limit < 0 {
limit = 0
}
total := int64(len(data))
// Reset fields
pagination.Next = ""
pagination.Previous = ""
pagination.Partial = false
pagination.Total = &total
pagination.First = ""
if len(data) == 0 {
return data
}
// startIndex is guaranteed to be a valid index
startIndex := int64(0)
if pagination.Marker != "" {
for i, item := range data {
id, _ := item["id"].(string)
if id == pagination.Marker {
startIndex = int64(i)
break
}
}
}
previousIndex := startIndex - limit
if previousIndex <= 0 {
previousIndex = 0
}
nextIndex := startIndex + limit
if nextIndex > int64(len(data)) {
nextIndex = int64(len(data))
}
if previousIndex < startIndex {
pagination.Previous, _ = data[previousIndex]["id"].(string)
}
if nextIndex > startIndex && nextIndex < int64(len(data)) {
pagination.Next, _ = data[nextIndex]["id"].(string)
}
if startIndex > 0 || nextIndex < int64(len(data)) {
pagination.Partial = true
}
if pagination.Partial {
pagination.First, _ = data[0]["id"].(string)
lastIndex := int64(len(data)) - limit
if lastIndex > 0 && lastIndex < int64(len(data)) {
pagination.Last, _ = data[lastIndex]["id"].(string)
}
}
return data[startIndex:nextIndex]
}

View File

@@ -1,28 +0,0 @@
package handler
import (
"net/http"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
)
func UpdateHandler(apiContext *types.APIContext, next types.RequestHandler) error {
data, err := ParseAndValidateBody(apiContext, false)
if err != nil {
return err
}
store := apiContext.Schema.Store
if store == nil {
return httperror.NewAPIError(httperror.NotFound, "no store found")
}
data, err = store.Update(apiContext, apiContext.Schema, data, apiContext.ID)
if err != nil {
return err
}
apiContext.WriteResponse(http.StatusOK, data)
return nil
}

View File

@@ -1,59 +0,0 @@
package handler
import (
"github.com/rancher/norman/parse"
"github.com/rancher/norman/parse/builder"
"github.com/rancher/norman/types"
)
func ParseAndValidateBody(apiContext *types.APIContext, create bool) (map[string]interface{}, error) {
data, err := parse.Body(apiContext.Request)
if err != nil {
return nil, err
}
if create {
for key, value := range apiContext.SubContextAttributeProvider.Create(apiContext, apiContext.Schema) {
if data == nil {
data = map[string]interface{}{}
}
data[key] = value
}
}
b := builder.NewBuilder(apiContext)
op := builder.Create
if !create {
op = builder.Update
}
if apiContext.Schema.InputFormatter != nil {
err = apiContext.Schema.InputFormatter(apiContext, apiContext.Schema, data, create)
if err != nil {
return nil, err
}
}
data, err = b.Construct(apiContext.Schema, data, op)
if err != nil {
return nil, err
}
return data, nil
}
func ParseAndValidateActionBody(apiContext *types.APIContext, actionInputSchema *types.Schema) (map[string]interface{}, error) {
data, err := parse.Body(apiContext.Request)
if err != nil {
return nil, err
}
b := builder.NewBuilder(apiContext)
op := builder.Create
data, err = b.Construct(actionInputSchema, data, op)
if err != nil {
return nil, err
}
return data, nil
}

View File

@@ -1,263 +0,0 @@
package api
import (
"net/http"
"sync"
"github.com/rancher/norman/api/access"
"github.com/rancher/norman/api/builtin"
"github.com/rancher/norman/api/handler"
"github.com/rancher/norman/api/writer"
"github.com/rancher/norman/authorization"
"github.com/rancher/norman/httperror"
ehandler "github.com/rancher/norman/httperror/handler"
"github.com/rancher/norman/parse"
"github.com/rancher/norman/store/wrapper"
"github.com/rancher/norman/types"
)
type StoreWrapper func(types.Store) types.Store
type Parser func(rw http.ResponseWriter, req *http.Request) (*types.APIContext, error)
type Server struct {
initBuiltin sync.Once
IgnoreBuiltin bool
Parser Parser
Resolver parse.ResolverFunc
SubContextAttributeProvider types.SubContextAttributeProvider
ResponseWriters map[string]ResponseWriter
Schemas *types.Schemas
QueryFilter types.QueryFilter
StoreWrapper StoreWrapper
URLParser parse.URLParser
Defaults Defaults
AccessControl types.AccessControl
}
type Defaults struct {
ActionHandler types.ActionHandler
ListHandler types.RequestHandler
LinkHandler types.RequestHandler
CreateHandler types.RequestHandler
DeleteHandler types.RequestHandler
UpdateHandler types.RequestHandler
Store types.Store
ErrorHandler types.ErrorHandler
}
func NewAPIServer() *Server {
s := &Server{
Schemas: types.NewSchemas(),
ResponseWriters: map[string]ResponseWriter{
"json": &writer.EncodingResponseWriter{
ContentType: "application/json",
Encoder: types.JSONEncoder,
},
"html": &writer.HTMLResponseWriter{
EncodingResponseWriter: writer.EncodingResponseWriter{
Encoder: types.JSONEncoder,
ContentType: "application/json",
},
},
"yaml": &writer.EncodingResponseWriter{
ContentType: "application/yaml",
Encoder: types.YAMLEncoder,
},
},
SubContextAttributeProvider: &parse.DefaultSubContextAttributeProvider{},
Resolver: parse.DefaultResolver,
AccessControl: &authorization.AllAccess{},
Defaults: Defaults{
CreateHandler: handler.CreateHandler,
DeleteHandler: handler.DeleteHandler,
UpdateHandler: handler.UpdateHandler,
ListHandler: handler.ListHandler,
LinkHandler: func(*types.APIContext, types.RequestHandler) error {
return httperror.NewAPIError(httperror.NotFound, "Link not found")
},
ErrorHandler: ehandler.ErrorHandler,
},
StoreWrapper: wrapper.Wrap,
URLParser: parse.DefaultURLParser,
QueryFilter: handler.QueryFilter,
}
s.Schemas.AddHook = s.setupDefaults
s.Parser = s.parser
return s
}
func (s *Server) parser(rw http.ResponseWriter, req *http.Request) (*types.APIContext, error) {
ctx, err := parse.Parse(rw, req, s.Schemas, s.URLParser, s.Resolver)
ctx.ResponseWriter = s.ResponseWriters[ctx.ResponseFormat]
if ctx.ResponseWriter == nil {
ctx.ResponseWriter = s.ResponseWriters["json"]
}
if ctx.QueryFilter == nil {
ctx.QueryFilter = s.QueryFilter
}
if ctx.SubContextAttributeProvider == nil {
ctx.SubContextAttributeProvider = s.SubContextAttributeProvider
}
ctx.AccessControl = s.AccessControl
return ctx, err
}
func (s *Server) AddSchemas(schemas *types.Schemas) error {
if schemas.Err() != nil {
return schemas.Err()
}
s.initBuiltin.Do(func() {
if s.IgnoreBuiltin {
return
}
for _, schema := range builtin.Schemas.Schemas() {
s.Schemas.AddSchema(*schema)
}
})
for _, schema := range schemas.Schemas() {
s.Schemas.AddSchema(*schema)
}
return s.Schemas.Err()
}
func (s *Server) setupDefaults(schema *types.Schema) {
if schema.ActionHandler == nil {
schema.ActionHandler = s.Defaults.ActionHandler
}
if schema.Store == nil {
schema.Store = s.Defaults.Store
}
if schema.ListHandler == nil {
schema.ListHandler = s.Defaults.ListHandler
}
if schema.LinkHandler == nil {
schema.LinkHandler = s.Defaults.LinkHandler
}
if schema.CreateHandler == nil {
schema.CreateHandler = s.Defaults.CreateHandler
}
if schema.UpdateHandler == nil {
schema.UpdateHandler = s.Defaults.UpdateHandler
}
if schema.DeleteHandler == nil {
schema.DeleteHandler = s.Defaults.DeleteHandler
}
if schema.ErrorHandler == nil {
schema.ErrorHandler = s.Defaults.ErrorHandler
}
if schema.Store != nil && s.StoreWrapper != nil {
schema.Store = s.StoreWrapper(schema.Store)
}
}
func (s *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if apiResponse, err := s.handle(rw, req); err != nil {
s.handleError(apiResponse, err)
}
}
func (s *Server) handle(rw http.ResponseWriter, req *http.Request) (*types.APIContext, error) {
apiRequest, err := s.Parser(rw, req)
if err != nil {
return apiRequest, err
}
if err := CheckCSRF(apiRequest); err != nil {
return apiRequest, err
}
action, err := ValidateAction(apiRequest)
if err != nil {
return apiRequest, err
}
if apiRequest.Schema == nil {
return apiRequest, nil
}
if action == nil && apiRequest.Type != "" {
var handler types.RequestHandler
var nextHandler types.RequestHandler
if apiRequest.Link == "" {
switch apiRequest.Method {
case http.MethodGet:
if apiRequest.ID == "" {
if err := apiRequest.AccessControl.CanList(apiRequest, apiRequest.Schema); err != nil {
return apiRequest, err
}
} else {
if err := apiRequest.AccessControl.CanGet(apiRequest, apiRequest.Schema); err != nil {
return apiRequest, err
}
}
handler = apiRequest.Schema.ListHandler
nextHandler = s.Defaults.ListHandler
case http.MethodPost:
if err := apiRequest.AccessControl.CanCreate(apiRequest, apiRequest.Schema); err != nil {
return apiRequest, err
}
handler = apiRequest.Schema.CreateHandler
nextHandler = s.Defaults.CreateHandler
case http.MethodPut:
if err := apiRequest.AccessControl.CanUpdate(apiRequest, nil, apiRequest.Schema); err != nil {
return apiRequest, err
}
handler = apiRequest.Schema.UpdateHandler
nextHandler = s.Defaults.UpdateHandler
case http.MethodDelete:
if err := apiRequest.AccessControl.CanDelete(apiRequest, nil, apiRequest.Schema); err != nil {
return apiRequest, err
}
handler = apiRequest.Schema.DeleteHandler
nextHandler = s.Defaults.DeleteHandler
}
} else {
handler = apiRequest.Schema.ListHandler
nextHandler = s.Defaults.ListHandler
}
if handler == nil {
return apiRequest, httperror.NewAPIError(httperror.NotFound, "")
}
return apiRequest, handler(apiRequest, nextHandler)
} else if action != nil {
return apiRequest, handleAction(action, apiRequest)
}
return apiRequest, nil
}
func handleAction(action *types.Action, context *types.APIContext) error {
if context.ID != "" {
if err := access.ByID(context, context.Version, context.Type, context.ID, nil); err != nil {
return err
}
}
return context.Schema.ActionHandler(context.Action, action, context)
}
func (s *Server) handleError(apiRequest *types.APIContext, err error) {
if apiRequest.Schema == nil {
s.Defaults.ErrorHandler(apiRequest, err)
} else if apiRequest.Schema.ErrorHandler != nil {
apiRequest.Schema.ErrorHandler(apiRequest, err)
}
}

View File

@@ -1,7 +0,0 @@
package api
import "github.com/rancher/norman/types"
type ResponseWriter interface {
Write(apiContext *types.APIContext, code int, obj interface{})
}

View File

@@ -1,83 +0,0 @@
package api
import (
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/parse"
"github.com/rancher/norman/types"
)
const (
csrfCookie = "CSRF"
csrfHeader = "X-API-CSRF"
)
func ValidateAction(request *types.APIContext) (*types.Action, error) {
if request.Action == "" || request.Link != "" || request.Method != http.MethodPost {
return nil, nil
}
actions := request.Schema.CollectionActions
if request.ID != "" {
actions = request.Schema.ResourceActions
}
action, ok := actions[request.Action]
if !ok {
return nil, httperror.NewAPIError(httperror.InvalidAction, fmt.Sprintf("Invalid action: %s", request.Action))
}
if request.ID != "" && request.ReferenceValidator != nil {
resource := request.ReferenceValidator.Lookup(request.Type, request.ID)
if resource == nil {
return nil, httperror.NewAPIError(httperror.NotFound, fmt.Sprintf("Failed to find type: %s id: %s", request.Type, request.ID))
}
if _, ok := resource.Actions[request.Action]; !ok {
return nil, httperror.NewAPIError(httperror.InvalidAction, fmt.Sprintf("Invalid action: %s", request.Action))
}
}
return &action, nil
}
func CheckCSRF(apiContext *types.APIContext) error {
if !parse.IsBrowser(apiContext.Request, false) {
return nil
}
cookie, err := apiContext.Request.Cookie(csrfCookie)
if err == http.ErrNoCookie {
bytes := make([]byte, 5)
_, err := rand.Read(bytes)
if err != nil {
return httperror.WrapAPIError(err, httperror.ServerError, "Failed in CSRF processing")
}
cookie = &http.Cookie{
Name: csrfCookie,
Value: hex.EncodeToString(bytes),
}
} else if err != nil {
return httperror.NewAPIError(httperror.InvalidCSRFToken, "Failed to parse cookies")
} else if apiContext.Method != http.MethodGet {
/*
* Very important to use apiContext.Method and not apiContext.Request.Method. The client can override the HTTP method with _method
*/
if cookie.Value == apiContext.Request.Header.Get(csrfHeader) {
// Good
} else if cookie.Value == apiContext.Request.URL.Query().Get(csrfCookie) {
// Good
} else {
return httperror.NewAPIError(httperror.InvalidCSRFToken, "Invalid CSRF token")
}
}
cookie.Path = "/"
http.SetCookie(apiContext.Response, cookie)
return nil
}

View File

@@ -1,30 +0,0 @@
package writer
import (
"github.com/rancher/norman/api/builtin"
"github.com/rancher/norman/types"
)
func AddCommonResponseHeader(apiContext *types.APIContext) error {
addExpires(apiContext)
return addSchemasHeader(apiContext)
}
func addSchemasHeader(apiContext *types.APIContext) error {
schema := apiContext.Schemas.Schema(&builtin.Version, "schema")
if schema == nil {
return nil
}
version := apiContext.SchemasVersion
if version == nil {
version = apiContext.Version
}
apiContext.Response.Header().Set("X-Api-Schemas", apiContext.URLBuilder.Collection(schema, version))
return nil
}
func addExpires(apiContext *types.APIContext) {
apiContext.Response.Header().Set("Expires", "Wed 24 Feb 1982 18:42:00 GMT")
}

View File

@@ -1,48 +0,0 @@
package writer
import (
"strings"
"github.com/rancher/norman/api/builtin"
"github.com/rancher/norman/types"
)
var (
start = `
<!DOCTYPE html>
<!-- If you are reading this, there is a good chance you would prefer sending an
"Accept: application/json" header and receiving actual JSON responses. -->
<link rel="stylesheet" type="text/css" href="https://releases.rancher.com/api-ui/1.1.5/ui.min.css" />
<script src="https://releases.rancher.com/api-ui/1.1.5/ui.min.js"></script>
<script>
var user = "admin";
var curlUser='${CATTLE_ACCESS_KEY}:${CATTLE_SECRET_KEY}';
var schemas="%SCHEMAS%";
var data =
`
end = []byte(`</script>
`)
)
type HTMLResponseWriter struct {
EncodingResponseWriter
}
func (h *HTMLResponseWriter) start(apiContext *types.APIContext, code int, obj interface{}) {
AddCommonResponseHeader(apiContext)
apiContext.Response.Header().Set("content-type", "text/html")
apiContext.Response.WriteHeader(code)
}
func (h *HTMLResponseWriter) Write(apiContext *types.APIContext, code int, obj interface{}) {
h.start(apiContext, code, obj)
schemaSchema := apiContext.Schemas.Schema(&builtin.Version, "schema")
if schemaSchema != nil {
headerString := strings.Replace(start, "%SCHEMAS%", apiContext.URLBuilder.Collection(schemaSchema, apiContext.Version), 1)
apiContext.Response.Write([]byte(headerString))
}
h.Body(apiContext, apiContext.Response, obj)
if schemaSchema != nil {
apiContext.Response.Write(end)
}
}

View File

@@ -1,234 +0,0 @@
package writer
import (
"fmt"
"io"
"net/http"
"github.com/rancher/norman/parse"
"github.com/rancher/norman/parse/builder"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/definition"
"github.com/sirupsen/logrus"
)
type EncodingResponseWriter struct {
ContentType string
Encoder func(io.Writer, interface{}) error
}
func (j *EncodingResponseWriter) start(apiContext *types.APIContext, code int, obj interface{}) {
AddCommonResponseHeader(apiContext)
apiContext.Response.Header().Set("content-type", j.ContentType)
apiContext.Response.WriteHeader(code)
}
func (j *EncodingResponseWriter) Write(apiContext *types.APIContext, code int, obj interface{}) {
j.start(apiContext, code, obj)
j.Body(apiContext, apiContext.Response, obj)
}
func (j *EncodingResponseWriter) Body(apiContext *types.APIContext, writer io.Writer, obj interface{}) error {
return j.VersionBody(apiContext, apiContext.Version, writer, obj)
}
func (j *EncodingResponseWriter) VersionBody(apiContext *types.APIContext, version *types.APIVersion, writer io.Writer, obj interface{}) error {
var output interface{}
builder := builder.NewBuilder(apiContext)
builder.Version = version
switch v := obj.(type) {
case []interface{}:
output = j.writeInterfaceSlice(builder, apiContext, v)
case []map[string]interface{}:
output = j.writeMapSlice(builder, apiContext, v)
case map[string]interface{}:
output = j.convert(builder, apiContext, v)
case types.RawResource:
output = v
}
if output != nil {
return j.Encoder(writer, output)
}
return nil
}
func (j *EncodingResponseWriter) writeMapSlice(builder *builder.Builder, apiContext *types.APIContext, input []map[string]interface{}) *types.GenericCollection {
collection := newCollection(apiContext)
for _, value := range input {
converted := j.convert(builder, apiContext, value)
if converted != nil {
collection.Data = append(collection.Data, converted)
}
}
if apiContext.Schema.CollectionFormatter != nil {
apiContext.Schema.CollectionFormatter(apiContext, collection)
}
return collection
}
func (j *EncodingResponseWriter) writeInterfaceSlice(builder *builder.Builder, apiContext *types.APIContext, input []interface{}) *types.GenericCollection {
collection := newCollection(apiContext)
for _, value := range input {
switch v := value.(type) {
case map[string]interface{}:
converted := j.convert(builder, apiContext, v)
if converted != nil {
collection.Data = append(collection.Data, converted)
}
default:
collection.Data = append(collection.Data, v)
}
}
if apiContext.Schema.CollectionFormatter != nil {
apiContext.Schema.CollectionFormatter(apiContext, collection)
}
return collection
}
func toString(val interface{}) string {
if val == nil {
return ""
}
return fmt.Sprint(val)
}
func (j *EncodingResponseWriter) convert(b *builder.Builder, context *types.APIContext, input map[string]interface{}) *types.RawResource {
schema := context.Schemas.Schema(context.Version, definition.GetFullType(input))
if schema == nil {
return nil
}
op := builder.List
if context.Method == http.MethodPost {
op = builder.ListForCreate
}
data, err := b.Construct(schema, input, op)
if err != nil {
logrus.Errorf("Failed to construct object on output: %v", err)
return nil
}
rawResource := &types.RawResource{
ID: toString(input["id"]),
Type: schema.ID,
Schema: schema,
Links: map[string]string{},
Actions: map[string]string{},
Values: data,
ActionLinks: context.Request.Header.Get("X-API-Action-Links") != "",
}
j.addLinks(b, schema, context, input, rawResource)
if schema.Formatter != nil {
schema.Formatter(context, rawResource)
}
return rawResource
}
func (j *EncodingResponseWriter) addLinks(b *builder.Builder, schema *types.Schema, context *types.APIContext, input map[string]interface{}, rawResource *types.RawResource) {
if rawResource.ID == "" {
return
}
self := context.URLBuilder.ResourceLink(rawResource)
rawResource.Links["self"] = self
if context.AccessControl.CanUpdate(context, input, schema) == nil {
rawResource.Links["update"] = self
}
if context.AccessControl.CanDelete(context, input, schema) == nil {
rawResource.Links["remove"] = self
}
subContextVersion := context.Schemas.SubContextVersionForSchema(schema)
for _, backRef := range context.Schemas.References(schema) {
if backRef.Schema.CanList(context) != nil {
continue
}
if subContextVersion == nil {
rawResource.Links[backRef.Schema.PluralName] = context.URLBuilder.FilterLink(backRef.Schema, backRef.FieldName, rawResource.ID)
} else {
rawResource.Links[backRef.Schema.PluralName] = context.URLBuilder.SubContextCollection(schema, rawResource.ID, backRef.Schema)
}
}
if subContextVersion != nil {
for _, subSchema := range context.Schemas.SchemasForVersion(*subContextVersion) {
if subSchema.CanList(context) == nil {
rawResource.Links[subSchema.PluralName] = context.URLBuilder.SubContextCollection(schema, rawResource.ID, subSchema)
}
}
}
}
func newCollection(apiContext *types.APIContext) *types.GenericCollection {
result := &types.GenericCollection{
Collection: types.Collection{
Type: "collection",
ResourceType: apiContext.Type,
CreateTypes: map[string]string{},
Links: map[string]string{
"self": apiContext.URLBuilder.Current(),
},
Actions: map[string]string{},
},
Data: []interface{}{},
}
if apiContext.Method == http.MethodGet {
if apiContext.AccessControl.CanCreate(apiContext, apiContext.Schema) == nil {
result.CreateTypes[apiContext.Schema.ID] = apiContext.URLBuilder.Collection(apiContext.Schema, apiContext.Version)
}
}
opts := parse.QueryOptions(apiContext, apiContext.Schema)
result.Sort = &opts.Sort
result.Sort.Reverse = apiContext.URLBuilder.ReverseSort(result.Sort.Order)
result.Sort.Links = map[string]string{}
result.Pagination = opts.Pagination
result.Filters = map[string][]types.Condition{}
for _, cond := range opts.Conditions {
filters := result.Filters[cond.Field]
result.Filters[cond.Field] = append(filters, cond.ToCondition())
}
for name := range apiContext.Schema.CollectionFilters {
if _, ok := result.Filters[name]; !ok {
result.Filters[name] = nil
}
}
for queryField := range apiContext.Schema.CollectionFilters {
field, ok := apiContext.Schema.ResourceFields[queryField]
if ok && (field.Type == "string" || field.Type == "enum") {
result.Sort.Links[queryField] = apiContext.URLBuilder.Sort(queryField)
}
}
if result.Pagination != nil && result.Pagination.Partial {
if result.Pagination.Next != "" {
result.Pagination.Next = apiContext.URLBuilder.Marker(result.Pagination.Next)
}
if result.Pagination.Previous != "" {
result.Pagination.Previous = apiContext.URLBuilder.Marker(result.Pagination.Previous)
}
if result.Pagination.First != "" {
result.Pagination.First = apiContext.URLBuilder.Marker(result.Pagination.First)
}
if result.Pagination.Last != "" {
result.Pagination.Last = apiContext.URLBuilder.Marker(result.Pagination.Last)
}
}
return result
}

View File

@@ -1,62 +0,0 @@
package authorization
import (
"net/http"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/slice"
)
type AllAccess struct {
}
func (*AllAccess) CanCreate(apiContext *types.APIContext, schema *types.Schema) error {
if slice.ContainsString(schema.CollectionMethods, http.MethodPost) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not create "+schema.ID)
}
func (*AllAccess) CanGet(apiContext *types.APIContext, schema *types.Schema) error {
if slice.ContainsString(schema.ResourceMethods, http.MethodGet) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not get "+schema.ID)
}
func (*AllAccess) CanList(apiContext *types.APIContext, schema *types.Schema) error {
if slice.ContainsString(schema.CollectionMethods, http.MethodGet) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not list "+schema.ID)
}
func (*AllAccess) CanUpdate(apiContext *types.APIContext, obj map[string]interface{}, schema *types.Schema) error {
if slice.ContainsString(schema.ResourceMethods, http.MethodPut) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not update "+schema.ID)
}
func (*AllAccess) CanDelete(apiContext *types.APIContext, obj map[string]interface{}, schema *types.Schema) error {
if slice.ContainsString(schema.ResourceMethods, http.MethodDelete) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not delete "+schema.ID)
}
func (*AllAccess) CanDo(apiGroup, resource, verb string, apiContext *types.APIContext, obj map[string]interface{}, schema *types.Schema) error {
if slice.ContainsString(schema.ResourceMethods, verb) {
return nil
}
return httperror.NewAPIError(httperror.PermissionDenied, "can not perform "+verb+" "+schema.ID)
}
func (*AllAccess) Filter(apiContext *types.APIContext, schema *types.Schema, obj map[string]interface{}, context map[string]string) map[string]interface{} {
return obj
}
func (*AllAccess) FilterList(apiContext *types.APIContext, schema *types.Schema, obj []map[string]interface{}, context map[string]string) []map[string]interface{} {
return obj
}

View File

@@ -1,373 +0,0 @@
package clientbase
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"time"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
"github.com/rancher/norman/types"
)
const (
SELF = "self"
COLLECTION = "collection"
)
var (
Debug = false
)
type APIBaseClientInterface interface {
Websocket(url string, headers map[string][]string) (*websocket.Conn, *http.Response, error)
List(schemaType string, opts *types.ListOpts, respObject interface{}) error
Post(url string, createObj interface{}, respObject interface{}) error
GetLink(resource types.Resource, link string, respObject interface{}) error
Create(schemaType string, createObj interface{}, respObject interface{}) error
Update(schemaType string, existing *types.Resource, updates interface{}, respObject interface{}) error
Replace(schemaType string, existing *types.Resource, updates interface{}, respObject interface{}) error
ByID(schemaType string, id string, respObject interface{}) error
Delete(existing *types.Resource) error
Reload(existing *types.Resource, output interface{}) error
Action(schemaType string, action string, existing *types.Resource, inputObject, respObject interface{}) error
}
type APIBaseClient struct {
Ops *APIOperations
Opts *ClientOpts
Types map[string]types.Schema
}
type ClientOpts struct {
URL string
AccessKey string
SecretKey string
TokenKey string
Timeout time.Duration
HTTPClient *http.Client
WSDialer *websocket.Dialer
CACerts string
Insecure bool
}
func (c *ClientOpts) getAuthHeader() string {
if c.TokenKey != "" {
return "Bearer " + c.TokenKey
}
if c.AccessKey != "" && c.SecretKey != "" {
s := c.AccessKey + ":" + c.SecretKey
return "Basic " + base64.StdEncoding.EncodeToString([]byte(s))
}
return ""
}
type APIError struct {
StatusCode int
URL string
Msg string
Status string
Body string
}
func (e *APIError) Error() string {
return e.Msg
}
func IsNotFound(err error) bool {
apiError, ok := err.(*APIError)
if !ok {
return false
}
return apiError.StatusCode == http.StatusNotFound
}
func NewAPIError(resp *http.Response, url string) *APIError {
contents, err := ioutil.ReadAll(resp.Body)
var body string
if err != nil {
body = "Unreadable body."
} else {
body = string(contents)
}
data := map[string]interface{}{}
if json.Unmarshal(contents, &data) == nil {
delete(data, "id")
delete(data, "links")
delete(data, "actions")
delete(data, "type")
delete(data, "status")
buf := &bytes.Buffer{}
for k, v := range data {
if v == nil {
continue
}
if buf.Len() > 0 {
buf.WriteString(", ")
}
fmt.Fprintf(buf, "%s=%v", k, v)
}
body = buf.String()
}
formattedMsg := fmt.Sprintf("Bad response statusCode [%d]. Status [%s]. Body: [%s] from [%s]",
resp.StatusCode, resp.Status, body, url)
return &APIError{
URL: url,
Msg: formattedMsg,
StatusCode: resp.StatusCode,
Status: resp.Status,
Body: body,
}
}
func contains(array []string, item string) bool {
for _, check := range array {
if check == item {
return true
}
}
return false
}
func appendFilters(urlString string, filters map[string]interface{}) (string, error) {
if len(filters) == 0 {
return urlString, nil
}
u, err := url.Parse(urlString)
if err != nil {
return "", err
}
q := u.Query()
for k, v := range filters {
if l, ok := v.([]string); ok {
for _, v := range l {
q.Add(k, v)
}
} else {
q.Add(k, fmt.Sprintf("%v", v))
}
}
u.RawQuery = q.Encode()
return u.String(), nil
}
func NewAPIClient(opts *ClientOpts) (APIBaseClient, error) {
var err error
result := APIBaseClient{
Types: map[string]types.Schema{},
}
client := opts.HTTPClient
if client == nil {
client = &http.Client{}
}
if opts.Timeout == 0 {
opts.Timeout = time.Minute
}
client.Timeout = opts.Timeout
if opts.CACerts != "" {
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(opts.CACerts))
if !ok {
return result, err
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: roots,
},
}
client.Transport = tr
}
if opts.Insecure {
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: opts.Insecure,
},
}
client.Transport = tr
}
req, err := http.NewRequest("GET", opts.URL, nil)
if err != nil {
return result, err
}
req.Header.Add("Authorization", opts.getAuthHeader())
resp, err := client.Do(req)
if err != nil {
return result, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return result, NewAPIError(resp, opts.URL)
}
schemasURLs := resp.Header.Get("X-API-Schemas")
if len(schemasURLs) == 0 {
return result, errors.New("Failed to find schema at [" + opts.URL + "]")
}
if schemasURLs != opts.URL {
req, err = http.NewRequest("GET", schemasURLs, nil)
if err != nil {
return result, err
}
req.Header.Add("Authorization", opts.getAuthHeader())
if Debug {
fmt.Println("GET " + req.URL.String())
}
resp, err = client.Do(req)
if err != nil {
return result, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return result, NewAPIError(resp, schemasURLs)
}
}
var schemas types.SchemaCollection
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return result, err
}
if Debug {
fmt.Println("Response <= " + string(bytes))
}
err = json.Unmarshal(bytes, &schemas)
if err != nil {
return result, err
}
for _, schema := range schemas.Data {
result.Types[schema.ID] = schema
}
result.Opts = opts
result.Ops = &APIOperations{
Opts: opts,
Client: client,
Dialer: &websocket.Dialer{},
Types: result.Types,
}
if result.Opts.WSDialer != nil {
result.Ops.Dialer = result.Opts.WSDialer
}
ht, ok := client.Transport.(*http.Transport)
if ok {
result.Ops.Dialer.TLSClientConfig = ht.TLSClientConfig
}
return result, nil
}
func NewListOpts() *types.ListOpts {
return &types.ListOpts{
Filters: map[string]interface{}{},
}
}
func (a *APIBaseClient) Websocket(url string, headers map[string][]string) (*websocket.Conn, *http.Response, error) {
httpHeaders := http.Header{}
for k, v := range httpHeaders {
httpHeaders[k] = v
}
if a.Opts != nil {
httpHeaders.Add("Authorization", a.Opts.getAuthHeader())
}
if Debug {
fmt.Println("WS " + url)
}
return a.Ops.Dialer.Dial(url, http.Header(httpHeaders))
}
func (a *APIBaseClient) List(schemaType string, opts *types.ListOpts, respObject interface{}) error {
return a.Ops.DoList(schemaType, opts, respObject)
}
func (a *APIBaseClient) Post(url string, createObj interface{}, respObject interface{}) error {
return a.Ops.DoModify("POST", url, createObj, respObject)
}
func (a *APIBaseClient) GetLink(resource types.Resource, link string, respObject interface{}) error {
url := resource.Links[link]
if url == "" {
return fmt.Errorf("failed to find link: %s", link)
}
return a.Ops.DoGet(url, &types.ListOpts{}, respObject)
}
func (a *APIBaseClient) Create(schemaType string, createObj interface{}, respObject interface{}) error {
return a.Ops.DoCreate(schemaType, createObj, respObject)
}
func (a *APIBaseClient) Update(schemaType string, existing *types.Resource, updates interface{}, respObject interface{}) error {
return a.Ops.DoUpdate(schemaType, existing, updates, respObject)
}
func (a *APIBaseClient) Replace(schemaType string, existing *types.Resource, updates interface{}, respObject interface{}) error {
return a.Ops.DoReplace(schemaType, existing, updates, respObject)
}
func (a *APIBaseClient) ByID(schemaType string, id string, respObject interface{}) error {
return a.Ops.DoByID(schemaType, id, respObject)
}
func (a *APIBaseClient) Delete(existing *types.Resource) error {
if existing == nil {
return nil
}
return a.Ops.DoResourceDelete(existing.Type, existing)
}
func (a *APIBaseClient) Reload(existing *types.Resource, output interface{}) error {
selfURL, ok := existing.Links[SELF]
if !ok {
return fmt.Errorf("failed to find self URL of [%v]", existing)
}
return a.Ops.DoGet(selfURL, NewListOpts(), output)
}
func (a *APIBaseClient) Action(schemaType string, action string,
existing *types.Resource, inputObject, respObject interface{}) error {
return a.Ops.DoAction(schemaType, action, existing, inputObject, respObject)
}
func init() {
Debug = os.Getenv("RANCHER_CLIENT_DEBUG") == "true"
if Debug {
fmt.Println("Rancher client debug on")
}
}

View File

@@ -1,372 +0,0 @@
package clientbase
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
"github.com/rancher/norman/types"
)
type APIOperations struct {
Opts *ClientOpts
Types map[string]types.Schema
Client *http.Client
Dialer *websocket.Dialer
}
func (a *APIOperations) SetupRequest(req *http.Request) {
req.Header.Add("Authorization", a.Opts.getAuthHeader())
}
func (a *APIOperations) DoDelete(url string) error {
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}
a.SetupRequest(req)
resp, err := a.Client.Do(req)
if err != nil {
return err
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}()
if resp.StatusCode >= 300 {
return NewAPIError(resp, url)
}
return nil
}
func (a *APIOperations) DoGet(url string, opts *types.ListOpts, respObject interface{}) error {
if opts == nil {
opts = NewListOpts()
}
url, err := appendFilters(url, opts.Filters)
if err != nil {
return err
}
if Debug {
fmt.Println("GET " + url)
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
a.SetupRequest(req)
resp, err := a.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return NewAPIError(resp, url)
}
byteContent, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if Debug {
fmt.Println("Response <= " + string(byteContent))
}
if err := json.Unmarshal(byteContent, respObject); err != nil {
return errors.Wrap(err, fmt.Sprintf("Failed to parse: %s", byteContent))
}
return nil
}
func (a *APIOperations) DoList(schemaType string, opts *types.ListOpts, respObject interface{}) error {
schema, ok := a.Types[schemaType]
if !ok {
return errors.New("Unknown schema type [" + schemaType + "]")
}
if !contains(schema.CollectionMethods, "GET") {
return errors.New("Resource type [" + schemaType + "] is not listable")
}
collectionURL, ok := schema.Links["collection"]
if !ok {
return errors.New("Resource type [" + schemaType + "] does not have a collection URL")
}
return a.DoGet(collectionURL, opts, respObject)
}
func (a *APIOperations) DoNext(nextURL string, respObject interface{}) error {
return a.DoGet(nextURL, nil, respObject)
}
func (a *APIOperations) DoModify(method string, url string, createObj interface{}, respObject interface{}) error {
bodyContent, err := json.Marshal(createObj)
if err != nil {
return err
}
if Debug {
fmt.Println(method + " " + url)
fmt.Println("Request => " + string(bodyContent))
}
req, err := http.NewRequest(method, url, bytes.NewBuffer(bodyContent))
if err != nil {
return err
}
a.SetupRequest(req)
req.Header.Set("Content-Type", "application/json")
resp, err := a.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
return NewAPIError(resp, url)
}
byteContent, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if len(byteContent) > 0 {
if Debug {
fmt.Println("Response <= " + string(byteContent))
}
return json.Unmarshal(byteContent, respObject)
}
return nil
}
func (a *APIOperations) DoCreate(schemaType string, createObj interface{}, respObject interface{}) error {
if createObj == nil {
createObj = map[string]string{}
}
if respObject == nil {
respObject = &map[string]interface{}{}
}
schema, ok := a.Types[schemaType]
if !ok {
return errors.New("Unknown schema type [" + schemaType + "]")
}
if !contains(schema.CollectionMethods, "POST") {
return errors.New("Resource type [" + schemaType + "] is not creatable")
}
var collectionURL string
collectionURL, ok = schema.Links[COLLECTION]
if !ok {
// return errors.New("Failed to find collection URL for [" + schemaType + "]")
// This is a hack to address https://github.com/rancher/cattle/issues/254
re := regexp.MustCompile("schemas.*")
collectionURL = re.ReplaceAllString(schema.Links[SELF], schema.PluralName)
}
return a.DoModify("POST", collectionURL, createObj, respObject)
}
func (a *APIOperations) DoReplace(schemaType string, existing *types.Resource, updates interface{}, respObject interface{}) error {
return a.doUpdate(schemaType, true, existing, updates, respObject)
}
func (a *APIOperations) DoUpdate(schemaType string, existing *types.Resource, updates interface{}, respObject interface{}) error {
return a.doUpdate(schemaType, false, existing, updates, respObject)
}
func (a *APIOperations) doUpdate(schemaType string, replace bool, existing *types.Resource, updates interface{}, respObject interface{}) error {
if existing == nil {
return errors.New("Existing object is nil")
}
selfURL, ok := existing.Links[SELF]
if !ok {
return fmt.Errorf("failed to find self URL of [%v]", existing)
}
if replace {
u, err := url.Parse(selfURL)
if err != nil {
return fmt.Errorf("failed to parse url %s: %v", selfURL, err)
}
q := u.Query()
q.Set("_replace", "true")
u.RawQuery = q.Encode()
selfURL = u.String()
}
if updates == nil {
updates = map[string]string{}
}
if respObject == nil {
respObject = &map[string]interface{}{}
}
schema, ok := a.Types[schemaType]
if !ok {
return errors.New("Unknown schema type [" + schemaType + "]")
}
if !contains(schema.ResourceMethods, "PUT") {
return errors.New("Resource type [" + schemaType + "] is not updatable")
}
return a.DoModify("PUT", selfURL, updates, respObject)
}
func (a *APIOperations) DoByID(schemaType string, id string, respObject interface{}) error {
schema, ok := a.Types[schemaType]
if !ok {
return errors.New("Unknown schema type [" + schemaType + "]")
}
if !contains(schema.ResourceMethods, "GET") {
return errors.New("Resource type [" + schemaType + "] can not be looked up by ID")
}
collectionURL, ok := schema.Links[COLLECTION]
if !ok {
return errors.New("Failed to find collection URL for [" + schemaType + "]")
}
return a.DoGet(collectionURL+"/"+id, nil, respObject)
}
func (a *APIOperations) DoResourceDelete(schemaType string, existing *types.Resource) error {
schema, ok := a.Types[schemaType]
if !ok {
return errors.New("Unknown schema type [" + schemaType + "]")
}
if !contains(schema.ResourceMethods, "DELETE") {
return errors.New("Resource type [" + schemaType + "] can not be deleted")
}
selfURL, ok := existing.Links[SELF]
if !ok {
return fmt.Errorf("failed to find self URL of [%v]", existing)
}
return a.DoDelete(selfURL)
}
func (a *APIOperations) DoAction(schemaType string, action string,
existing *types.Resource, inputObject, respObject interface{}) error {
if existing == nil {
return errors.New("Existing object is nil")
}
actionURL, ok := existing.Actions[action]
if !ok {
return fmt.Errorf("action [%v] not available on [%v]", action, existing)
}
return a.doAction(schemaType, action, actionURL, inputObject, respObject)
}
func (a *APIOperations) DoCollectionAction(schemaType string, action string,
existing *types.Collection, inputObject, respObject interface{}) error {
if existing == nil {
return errors.New("Existing object is nil")
}
actionURL, ok := existing.Actions[action]
if !ok {
return fmt.Errorf("action [%v] not available on [%v]", action, existing)
}
return a.doAction(schemaType, action, actionURL, inputObject, respObject)
}
func (a *APIOperations) doAction(
schemaType string,
action string,
actionURL string,
inputObject interface{},
respObject interface{},
) error {
_, ok := a.Types[schemaType]
if !ok {
return errors.New("Unknown schema type [" + schemaType + "]")
}
var input io.Reader
if Debug {
fmt.Println("POST " + actionURL)
}
if inputObject != nil {
bodyContent, err := json.Marshal(inputObject)
if err != nil {
return err
}
if Debug {
fmt.Println("Request => " + string(bodyContent))
}
input = bytes.NewBuffer(bodyContent)
}
req, err := http.NewRequest("POST", actionURL, input)
if err != nil {
return err
}
a.SetupRequest(req)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Content-Length", "0")
resp, err := a.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
return NewAPIError(resp, actionURL)
}
byteContent, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if Debug {
fmt.Println("Response <= " + string(byteContent))
}
if nil != respObject {
return json.Unmarshal(byteContent, respObject)
}
return nil
}

View File

@@ -1,296 +0,0 @@
package condition
import (
"reflect"
"regexp"
"time"
"github.com/pkg/errors"
"github.com/rancher/norman/controller"
err2 "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
)
type Cond string
var temfileRegexp = regexp.MustCompile("/tmp/[-_a-zA-Z0-9]+")
func (c Cond) True(obj runtime.Object) {
setStatus(obj, string(c), "True")
}
func (c Cond) IsTrue(obj runtime.Object) bool {
return getStatus(obj, string(c)) == "True"
}
func (c Cond) LastUpdated(obj runtime.Object, ts string) {
setTS(obj, string(c), ts)
}
func (c Cond) GetLastUpdated(obj runtime.Object) string {
return getTS(obj, string(c))
}
func (c Cond) False(obj runtime.Object) {
setStatus(obj, string(c), "False")
}
func (c Cond) IsFalse(obj runtime.Object) bool {
return getStatus(obj, string(c)) == "False"
}
func (c Cond) GetStatus(obj runtime.Object) string {
return getStatus(obj, string(c))
}
func (c Cond) Unknown(obj runtime.Object) {
setStatus(obj, string(c), "Unknown")
}
func (c Cond) CreateUnknownIfNotExists(obj runtime.Object) {
condSlice := getValue(obj, "Status", "Conditions")
cond := findCond(condSlice, string(c))
if cond == nil {
c.Unknown(obj)
}
}
func (c Cond) IsUnknown(obj runtime.Object) bool {
return getStatus(obj, string(c)) == "Unknown"
}
func (c Cond) Reason(obj runtime.Object, reason string) {
cond := findOrCreateCond(obj, string(c))
getFieldValue(cond, "Reason").SetString(reason)
}
func (c Cond) SetMessageIfBlank(obj runtime.Object, message string) {
if c.GetMessage(obj) == "" {
c.Message(obj, message)
}
}
func (c Cond) Message(obj runtime.Object, message string) {
cond := findOrCreateCond(obj, string(c))
setValue(cond, "Message", message)
}
func (c Cond) GetMessage(obj runtime.Object) string {
cond := findOrNotCreateCond(obj, string(c))
if cond == nil {
return ""
}
return getFieldValue(*cond, "Message").String()
}
func (c Cond) ReasonAndMessageFromError(obj runtime.Object, err error) {
if err2.IsConflict(err) {
return
}
cond := findOrCreateCond(obj, string(c))
setValue(cond, "Message", err.Error())
switch ce := err.(type) {
case *conditionError:
setValue(cond, "Reason", ce.reason)
case *controller.ForgetError:
if ce.Reason != "" {
setValue(cond, "Reason", ce.Reason)
} else {
setValue(cond, "Reason", "Error")
}
default:
setValue(cond, "Reason", "Error")
}
}
func (c Cond) GetReason(obj runtime.Object) string {
cond := findOrNotCreateCond(obj, string(c))
if cond == nil {
return ""
}
return getFieldValue(*cond, "Reason").String()
}
func (c Cond) Once(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
if c.IsFalse(obj) {
return obj, &controller.ForgetError{
Err: errors.New(c.GetReason(obj)),
}
}
return c.DoUntilTrue(obj, f)
}
func (c Cond) DoUntilTrue(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
if c.IsTrue(obj) {
return obj, nil
}
return c.do(obj, f)
}
func (c Cond) Do(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
return c.do(obj, f)
}
func (c Cond) do(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
status := c.GetStatus(obj)
ts := c.GetLastUpdated(obj)
reason := c.GetReason(obj)
message := c.GetMessage(obj)
obj, err := c.doInternal(obj, f)
// This is to prevent non stop flapping of states and update
if status == c.GetStatus(obj) &&
reason == c.GetReason(obj) {
if message != c.GetMessage(obj) {
replaced := temfileRegexp.ReplaceAllString(c.GetMessage(obj), "file_path_redacted")
c.Message(obj, replaced)
}
if message == c.GetMessage(obj) {
c.LastUpdated(obj, ts)
}
}
return obj, err
}
func (c Cond) doInternal(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
if !c.IsFalse(obj) {
c.Unknown(obj)
}
newObj, err := f()
if newObj != nil && !reflect.ValueOf(newObj).IsNil() {
obj = newObj
}
if err != nil {
if _, ok := err.(*controller.ForgetError); ok {
if c.GetMessage(obj) == "" {
c.ReasonAndMessageFromError(obj, err)
}
return obj, err
}
c.False(obj)
c.ReasonAndMessageFromError(obj, err)
return obj, err
}
c.True(obj)
c.Reason(obj, "")
c.Message(obj, "")
return obj, nil
}
func touchTS(value reflect.Value) {
now := time.Now().Format(time.RFC3339)
getFieldValue(value, "LastUpdateTime").SetString(now)
}
func getStatus(obj interface{}, condName string) string {
cond := findOrNotCreateCond(obj, condName)
if cond == nil {
return ""
}
return getFieldValue(*cond, "Status").String()
}
func setTS(obj interface{}, condName, ts string) {
cond := findOrCreateCond(obj, condName)
getFieldValue(cond, "LastUpdateTime").SetString(ts)
}
func getTS(obj interface{}, condName string) string {
cond := findOrNotCreateCond(obj, condName)
if cond == nil {
return ""
}
return getFieldValue(*cond, "LastUpdateTime").String()
}
func setStatus(obj interface{}, condName, status string) {
cond := findOrCreateCond(obj, condName)
setValue(cond, "Status", status)
}
func setValue(cond reflect.Value, fieldName, newValue string) {
value := getFieldValue(cond, fieldName)
if value.String() != newValue {
value.SetString(newValue)
touchTS(cond)
}
}
func findOrNotCreateCond(obj interface{}, condName string) *reflect.Value {
condSlice := getValue(obj, "Status", "Conditions")
return findCond(condSlice, condName)
}
func findOrCreateCond(obj interface{}, condName string) reflect.Value {
condSlice := getValue(obj, "Status", "Conditions")
cond := findCond(condSlice, condName)
if cond != nil {
return *cond
}
newCond := reflect.New(condSlice.Type().Elem()).Elem()
newCond.FieldByName("Type").SetString(condName)
newCond.FieldByName("Status").SetString("Unknown")
condSlice.Set(reflect.Append(condSlice, newCond))
return *findCond(condSlice, condName)
}
func findCond(val reflect.Value, name string) *reflect.Value {
for i := 0; i < val.Len(); i++ {
cond := val.Index(i)
typeVal := getFieldValue(cond, "Type")
if typeVal.String() == name {
return &cond
}
}
return nil
}
func getValue(obj interface{}, name ...string) reflect.Value {
if obj == nil {
return reflect.Value{}
}
v := reflect.ValueOf(obj)
t := v.Type()
if t.Kind() == reflect.Ptr {
v = v.Elem()
t = v.Type()
}
field := v.FieldByName(name[0])
if len(name) == 1 {
return field
}
return getFieldValue(field, name[1:]...)
}
func getFieldValue(v reflect.Value, name ...string) reflect.Value {
field := v.FieldByName(name[0])
if len(name) == 1 {
return field
}
return getFieldValue(field, name[1:]...)
}
func Error(reason string, err error) error {
return &conditionError{
reason: reason,
message: err.Error(),
}
}
type conditionError struct {
reason string
message string
}
func (e *conditionError) Error() string {
return e.message
}

View File

@@ -1,73 +0,0 @@
package controller
import (
"reflect"
"strings"
)
func ObjectInCluster(cluster string, obj interface{}) bool {
var clusterName string
if c := getValue(obj, "ClusterName"); c.IsValid() {
clusterName = c.String()
}
if clusterName == "" {
if c := getValue(obj, "Spec", "ClusterName"); c.IsValid() {
clusterName = c.String()
}
}
if clusterName == "" {
if c := getValue(obj, "ProjectName"); c.IsValid() {
if parts := strings.SplitN(c.String(), ":", 2); len(parts) == 2 {
clusterName = parts[0]
}
}
}
if clusterName == "" {
if c := getValue(obj, "Spec", "ProjectName"); c.IsValid() {
if parts := strings.SplitN(c.String(), ":", 2); len(parts) == 2 {
clusterName = parts[0]
}
}
}
if clusterName == "" {
if a := getValue(obj, "Annotations"); a.IsValid() {
if c := a.MapIndex(reflect.ValueOf("field.cattle.io/projectId")); c.IsValid() {
if parts := strings.SplitN(c.String(), ":", 2); len(parts) == 2 {
clusterName = parts[0]
}
}
}
}
if clusterName == "" {
if c := getValue(obj, "Namespace"); c.IsValid() {
clusterName = c.String()
}
}
return clusterName == cluster
}
func getValue(obj interface{}, name ...string) reflect.Value {
v := reflect.ValueOf(obj)
t := v.Type()
if t.Kind() == reflect.Ptr {
v = v.Elem()
t = v.Type()
}
field := v.FieldByName(name[0])
if !field.IsValid() || len(name) == 1 {
return field
}
return getFieldValue(field, name[1:]...)
}
func getFieldValue(v reflect.Value, name ...string) reflect.Value {
field := v.FieldByName(name[0])
if len(name) == 1 {
return field
}
return getFieldValue(field, name[1:]...)
}

View File

@@ -1,10 +0,0 @@
package controller
type ForgetError struct {
Err error
Reason string
}
func (f *ForgetError) Error() string {
return f.Err.Error()
}

View File

@@ -1,291 +0,0 @@
package controller
import (
"context"
"fmt"
"os"
"strings"
"sync"
"time"
errors2 "github.com/pkg/errors"
"github.com/rancher/norman/metrics"
"github.com/rancher/norman/objectclient"
"github.com/rancher/norman/types"
"github.com/sirupsen/logrus"
"golang.org/x/time/rate"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
const MetricsQueueEnv = "NORMAN_QUEUE_METRICS"
const MetricsReflectorEnv = "NORMAN_REFLECTOR_METRICS"
var (
resyncPeriod = 2 * time.Hour
)
// Override the metrics providers
func init() {
if os.Getenv(MetricsQueueEnv) != "true" {
DisableControllerWorkqueuMetrics()
}
if os.Getenv(MetricsReflectorEnv) != "true" {
DisableControllerReflectorMetrics()
}
}
type HandlerFunc func(key string) error
type GenericController interface {
Informer() cache.SharedIndexInformer
AddHandler(name string, handler HandlerFunc)
HandlerCount() int
Enqueue(namespace, name string)
Sync(ctx context.Context) error
Start(ctx context.Context, threadiness int) error
}
type Backend interface {
List(opts metav1.ListOptions) (runtime.Object, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
ObjectFactory() objectclient.ObjectFactory
}
type handlerDef struct {
name string
handler HandlerFunc
}
type genericController struct {
sync.Mutex
informer cache.SharedIndexInformer
handlers []handlerDef
queue workqueue.RateLimitingInterface
name string
running bool
synced bool
}
func NewGenericController(name string, genericClient Backend) GenericController {
informer := cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: genericClient.List,
WatchFunc: genericClient.Watch,
},
genericClient.ObjectFactory().Object(), resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
rl := workqueue.NewMaxOfRateLimiter(
workqueue.NewItemExponentialFailureRateLimiter(500*time.Millisecond, 1000*time.Second),
// 10 qps, 100 bucket size. This is only for retry speed and its only the overall factor (not per item)
&workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
)
return &genericController{
informer: informer,
queue: workqueue.NewNamedRateLimitingQueue(rl, name),
name: name,
}
}
func (g *genericController) HandlerCount() int {
return len(g.handlers)
}
func (g *genericController) Informer() cache.SharedIndexInformer {
return g.informer
}
func (g *genericController) Enqueue(namespace, name string) {
if namespace == "" {
g.queue.Add(name)
} else {
g.queue.Add(namespace + "/" + name)
}
}
func (g *genericController) AddHandler(name string, handler HandlerFunc) {
g.handlers = append(g.handlers, handlerDef{
name: name,
handler: handler,
})
}
func (g *genericController) Sync(ctx context.Context) error {
g.Lock()
defer g.Unlock()
return g.sync(ctx)
}
func (g *genericController) sync(ctx context.Context) error {
if g.synced {
return nil
}
defer utilruntime.HandleCrash()
g.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: g.queueObject,
UpdateFunc: func(_, obj interface{}) {
g.queueObject(obj)
},
DeleteFunc: g.queueObject,
})
logrus.Debugf("Syncing %s Controller", g.name)
go g.informer.Run(ctx.Done())
if !cache.WaitForCacheSync(ctx.Done(), g.informer.HasSynced) {
return fmt.Errorf("failed to sync controller %s", g.name)
}
logrus.Debugf("Syncing %s Controller Done", g.name)
g.synced = true
return nil
}
func (g *genericController) Start(ctx context.Context, threadiness int) error {
g.Lock()
defer g.Unlock()
if !g.synced {
if err := g.sync(ctx); err != nil {
return err
}
}
if !g.running {
go g.run(ctx, threadiness)
}
g.running = true
return nil
}
func (g *genericController) queueObject(obj interface{}) {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err == nil {
g.queue.Add(key)
}
}
func (g *genericController) run(ctx context.Context, threadiness int) {
defer utilruntime.HandleCrash()
defer g.queue.ShutDown()
for i := 0; i < threadiness; i++ {
go wait.Until(g.runWorker, time.Second, ctx.Done())
}
<-ctx.Done()
logrus.Infof("Shutting down %s controller", g.name)
}
func (g *genericController) runWorker() {
for g.processNextWorkItem() {
}
}
func (g *genericController) processNextWorkItem() bool {
key, quit := g.queue.Get()
if quit {
return false
}
defer g.queue.Done(key)
// do your work on the key. This method will contains your "do stuff" logic
err := g.syncHandler(key.(string))
checkErr := err
if handlerErr, ok := checkErr.(*handlerError); ok {
checkErr = handlerErr.err
}
if _, ok := checkErr.(*ForgetError); err == nil || ok {
if ok {
logrus.Debugf("%v %v completed with dropped err: %v", g.name, key, err)
}
g.queue.Forget(key)
return true
}
if err := filterConflictsError(err); err != nil {
logrus.Errorf("%v %v %v", g.name, key, err)
}
g.queue.AddRateLimited(key)
return true
}
func ignoreError(err error, checkString bool) bool {
err = errors2.Cause(err)
if errors.IsConflict(err) {
return true
}
if _, ok := err.(*ForgetError); ok {
return true
}
if checkString {
return strings.HasSuffix(err.Error(), "please apply your changes to the latest version and try again")
}
return false
}
func filterConflictsError(err error) error {
if ignoreError(err, false) {
return nil
}
if errs, ok := errors2.Cause(err).(*types.MultiErrors); ok {
var newErrors []error
for _, err := range errs.Errors {
if !ignoreError(err, true) {
newErrors = append(newErrors)
}
}
return types.NewErrors(newErrors...)
}
return err
}
func (g *genericController) syncHandler(s string) (err error) {
defer utilruntime.RecoverFromPanic(&err)
var errs []error
for _, handler := range g.handlers {
logrus.Debugf("%s calling handler %s %s", g.name, handler.name, s)
metrics.IncTotalHandlerExecution(g.name, handler.name)
if err := handler.handler(s); err != nil {
if !ignoreError(err, false) {
metrics.IncTotalHandlerFailure(g.name, handler.name, s)
}
errs = append(errs, &handlerError{
name: handler.name,
err: err,
})
}
}
err = types.NewErrors(errs...)
return
}
type handlerError struct {
name string
err error
}
func (h *handlerError) Error() string {
return fmt.Sprintf("[%s] failed with : %v", h.name, h.err)
}
func (h *handlerError) Cause() error {
return h.err
}

View File

@@ -1,71 +0,0 @@
package controller
import (
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
type noopMetric struct{}
func (noopMetric) Inc() {}
func (noopMetric) Dec() {}
func (noopMetric) Observe(float64) {}
func (noopMetric) Set(float64) {}
type noopWorkqueueMetricsProvider struct{}
func (noopWorkqueueMetricsProvider) NewDepthMetric(name string) workqueue.GaugeMetric {
return noopMetric{}
}
func (noopWorkqueueMetricsProvider) NewAddsMetric(name string) workqueue.CounterMetric {
return noopMetric{}
}
func (noopWorkqueueMetricsProvider) NewLatencyMetric(name string) workqueue.SummaryMetric {
return noopMetric{}
}
func (noopWorkqueueMetricsProvider) NewWorkDurationMetric(name string) workqueue.SummaryMetric {
return noopMetric{}
}
func (noopWorkqueueMetricsProvider) NewRetriesMetric(name string) workqueue.CounterMetric {
return noopMetric{}
}
type noopCacheMetricsProvider struct{}
func (noopCacheMetricsProvider) NewListsMetric(name string) cache.CounterMetric { return noopMetric{} }
func (noopCacheMetricsProvider) NewListDurationMetric(name string) cache.SummaryMetric {
return noopMetric{}
}
func (noopCacheMetricsProvider) NewItemsInListMetric(name string) cache.SummaryMetric {
return noopMetric{}
}
func (noopCacheMetricsProvider) NewWatchesMetric(name string) cache.CounterMetric { return noopMetric{} }
func (noopCacheMetricsProvider) NewShortWatchesMetric(name string) cache.CounterMetric {
return noopMetric{}
}
func (noopCacheMetricsProvider) NewWatchDurationMetric(name string) cache.SummaryMetric {
return noopMetric{}
}
func (noopCacheMetricsProvider) NewItemsInWatchMetric(name string) cache.SummaryMetric {
return noopMetric{}
}
func (noopCacheMetricsProvider) NewLastResourceVersionMetric(name string) cache.GaugeMetric {
return noopMetric{}
}
func DisableAllControllerMetrics() {
DisableControllerReflectorMetrics()
DisableControllerWorkqueuMetrics()
}
func DisableControllerWorkqueuMetrics() {
workqueue.SetProvider(noopWorkqueueMetricsProvider{})
}
func DisableControllerReflectorMetrics() {
cache.SetReflectorMetricsProvider(noopCacheMetricsProvider{})
}

View File

@@ -1,40 +0,0 @@
package controller
import (
"context"
"golang.org/x/sync/errgroup"
)
type Starter interface {
Sync(ctx context.Context) error
Start(ctx context.Context, threadiness int) error
}
func SyncThenStart(ctx context.Context, threadiness int, starters ...Starter) error {
if err := Sync(ctx, starters...); err != nil {
return err
}
return Start(ctx, threadiness, starters...)
}
func Sync(ctx context.Context, starters ...Starter) error {
eg, _ := errgroup.WithContext(ctx)
for _, starter := range starters {
func(starter Starter) {
eg.Go(func() error {
return starter.Sync(ctx)
})
}(starter)
}
return eg.Wait()
}
func Start(ctx context.Context, threadiness int, starters ...Starter) error {
for _, starter := range starters {
if err := starter.Start(ctx, threadiness); err != nil {
return err
}
}
return nil
}

View File

@@ -1,66 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
"github.com/rancher/norman/api"
"github.com/rancher/norman/store/crd"
"github.com/rancher/norman/store/proxy"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/factory"
"k8s.io/client-go/tools/clientcmd"
)
type Foo struct {
types.Resource
Name string `json:"name"`
Foo string `json:"foo"`
SubThing Baz `json:"subThing"`
}
type Baz struct {
Name string `json:"name"`
}
var (
version = types.APIVersion{
Version: "v1",
Group: "example.core.cattle.io",
Path: "/example/v1",
}
Schemas = factory.Schemas(&version)
)
func main() {
kubeConfig, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG"))
if err != nil {
panic(err)
}
client, err := proxy.NewClientGetterFromConfig(*kubeConfig)
if err != nil {
panic(err)
}
crdFactory := crd.Factory{
ClientGetter: client,
}
Schemas.MustImportAndCustomize(&version, Foo{}, func(schema *types.Schema) {
if err := crdFactory.AssignStores(context.Background(), types.DefaultStorageContext, schema); err != nil {
panic(err)
}
})
server := api.NewAPIServer()
if err := server.AddSchemas(Schemas); err != nil {
panic(err)
}
fmt.Println("Listening on 0.0.0.0:1234")
http.ListenAndServe("0.0.0.0:1234", server)
}

View File

@@ -1,31 +0,0 @@
package generator
var clientTemplate = `package client
import (
"github.com/rancher/norman/clientbase"
)
type Client struct {
clientbase.APIBaseClient
{{range .schemas}}
{{- if . | hasGet }}{{.CodeName}} {{.CodeName}}Operations
{{end}}{{end}}}
func NewClient(opts *clientbase.ClientOpts) (*Client, error) {
baseClient, err := clientbase.NewAPIClient(opts)
if err != nil {
return nil, err
}
client := &Client{
APIBaseClient: baseClient,
}
{{range .schemas}}
{{- if . | hasGet }}client.{{.CodeName}} = new{{.CodeName}}Client(client)
{{end}}{{end}}
return client, nil
}
`

View File

@@ -1,265 +0,0 @@
package generator
var controllerTemplate = `package {{.schema.Version.Version}}
import (
"context"
{{.importPackage}}
"github.com/rancher/norman/objectclient"
"github.com/rancher/norman/controller"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/cache"
)
var (
{{.schema.CodeName}}GroupVersionKind = schema.GroupVersionKind{
Version: Version,
Group: GroupName,
Kind: "{{.schema.CodeName}}",
}
{{.schema.CodeName}}Resource = metav1.APIResource{
Name: "{{.schema.PluralName | toLower}}",
SingularName: "{{.schema.ID | toLower}}",
{{- if eq .schema.Scope "namespace" }}
Namespaced: true,
{{ else }}
Namespaced: false,
{{- end }}
Kind: {{.schema.CodeName}}GroupVersionKind.Kind,
}
)
type {{.schema.CodeName}}List struct {
metav1.TypeMeta %BACK%json:",inline"%BACK%
metav1.ListMeta %BACK%json:"metadata,omitempty"%BACK%
Items []{{.prefix}}{{.schema.CodeName}}
}
type {{.schema.CodeName}}HandlerFunc func(key string, obj *{{.prefix}}{{.schema.CodeName}}) error
type {{.schema.CodeName}}Lister interface {
List(namespace string, selector labels.Selector) (ret []*{{.prefix}}{{.schema.CodeName}}, err error)
Get(namespace, name string) (*{{.prefix}}{{.schema.CodeName}}, error)
}
type {{.schema.CodeName}}Controller interface {
Generic() controller.GenericController
Informer() cache.SharedIndexInformer
Lister() {{.schema.CodeName}}Lister
AddHandler(name string, handler {{.schema.CodeName}}HandlerFunc)
AddClusterScopedHandler(name, clusterName string, handler {{.schema.CodeName}}HandlerFunc)
Enqueue(namespace, name string)
Sync(ctx context.Context) error
Start(ctx context.Context, threadiness int) error
}
type {{.schema.CodeName}}Interface interface {
ObjectClient() *objectclient.ObjectClient
Create(*{{.prefix}}{{.schema.CodeName}}) (*{{.prefix}}{{.schema.CodeName}}, error)
GetNamespaced(namespace, name string, opts metav1.GetOptions) (*{{.prefix}}{{.schema.CodeName}}, error)
Get(name string, opts metav1.GetOptions) (*{{.prefix}}{{.schema.CodeName}}, error)
Update(*{{.prefix}}{{.schema.CodeName}}) (*{{.prefix}}{{.schema.CodeName}}, error)
Delete(name string, options *metav1.DeleteOptions) error
DeleteNamespaced(namespace, name string, options *metav1.DeleteOptions) error
List(opts metav1.ListOptions) (*{{.schema.CodeName}}List, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
DeleteCollection(deleteOpts *metav1.DeleteOptions, listOpts metav1.ListOptions) error
Controller() {{.schema.CodeName}}Controller
AddHandler(name string, sync {{.schema.CodeName}}HandlerFunc)
AddLifecycle(name string, lifecycle {{.schema.CodeName}}Lifecycle)
AddClusterScopedHandler(name, clusterName string, sync {{.schema.CodeName}}HandlerFunc)
AddClusterScopedLifecycle(name, clusterName string, lifecycle {{.schema.CodeName}}Lifecycle)
}
type {{.schema.ID}}Lister struct {
controller *{{.schema.ID}}Controller
}
func (l *{{.schema.ID}}Lister) List(namespace string, selector labels.Selector) (ret []*{{.prefix}}{{.schema.CodeName}}, err error) {
err = cache.ListAllByNamespace(l.controller.Informer().GetIndexer(), namespace, selector, func(obj interface{}) {
ret = append(ret, obj.(*{{.prefix}}{{.schema.CodeName}}))
})
return
}
func (l *{{.schema.ID}}Lister) Get(namespace, name string) (*{{.prefix}}{{.schema.CodeName}}, error) {
var key string
if namespace != "" {
key = namespace + "/" + name
} else {
key = name
}
obj, exists, err := l.controller.Informer().GetIndexer().GetByKey(key)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(schema.GroupResource{
Group: {{.schema.CodeName}}GroupVersionKind.Group,
Resource: "{{.schema.ID}}",
}, key)
}
return obj.(*{{.prefix}}{{.schema.CodeName}}), nil
}
type {{.schema.ID}}Controller struct {
controller.GenericController
}
func (c *{{.schema.ID}}Controller) Generic() controller.GenericController {
return c.GenericController
}
func (c *{{.schema.ID}}Controller) Lister() {{.schema.CodeName}}Lister {
return &{{.schema.ID}}Lister{
controller: c,
}
}
func (c *{{.schema.ID}}Controller) AddHandler(name string, handler {{.schema.CodeName}}HandlerFunc) {
c.GenericController.AddHandler(name, func(key string) error {
obj, exists, err := c.Informer().GetStore().GetByKey(key)
if err != nil {
return err
}
if !exists {
return handler(key, nil)
}
return handler(key, obj.(*{{.prefix}}{{.schema.CodeName}}))
})
}
func (c *{{.schema.ID}}Controller) AddClusterScopedHandler(name, cluster string, handler {{.schema.CodeName}}HandlerFunc) {
c.GenericController.AddHandler(name, func(key string) error {
obj, exists, err := c.Informer().GetStore().GetByKey(key)
if err != nil {
return err
}
if !exists {
return handler(key, nil)
}
if !controller.ObjectInCluster(cluster, obj) {
return nil
}
return handler(key, obj.(*{{.prefix}}{{.schema.CodeName}}))
})
}
type {{.schema.ID}}Factory struct {
}
func (c {{.schema.ID}}Factory) Object() runtime.Object {
return &{{.prefix}}{{.schema.CodeName}}{}
}
func (c {{.schema.ID}}Factory) List() runtime.Object {
return &{{.schema.CodeName}}List{}
}
func (s *{{.schema.ID}}Client) Controller() {{.schema.CodeName}}Controller {
s.client.Lock()
defer s.client.Unlock()
c, ok := s.client.{{.schema.ID}}Controllers[s.ns]
if ok {
return c
}
genericController := controller.NewGenericController({{.schema.CodeName}}GroupVersionKind.Kind+"Controller",
s.objectClient)
c = &{{.schema.ID}}Controller{
GenericController: genericController,
}
s.client.{{.schema.ID}}Controllers[s.ns] = c
s.client.starters = append(s.client.starters, c)
return c
}
type {{.schema.ID}}Client struct {
client *Client
ns string
objectClient *objectclient.ObjectClient
controller {{.schema.CodeName}}Controller
}
func (s *{{.schema.ID}}Client) ObjectClient() *objectclient.ObjectClient {
return s.objectClient
}
func (s *{{.schema.ID}}Client) Create(o *{{.prefix}}{{.schema.CodeName}}) (*{{.prefix}}{{.schema.CodeName}}, error) {
obj, err := s.objectClient.Create(o)
return obj.(*{{.prefix}}{{.schema.CodeName}}), err
}
func (s *{{.schema.ID}}Client) Get(name string, opts metav1.GetOptions) (*{{.prefix}}{{.schema.CodeName}}, error) {
obj, err := s.objectClient.Get(name, opts)
return obj.(*{{.prefix}}{{.schema.CodeName}}), err
}
func (s *{{.schema.ID}}Client) GetNamespaced(namespace, name string, opts metav1.GetOptions) (*{{.prefix}}{{.schema.CodeName}}, error) {
obj, err := s.objectClient.GetNamespaced(namespace, name, opts)
return obj.(*{{.prefix}}{{.schema.CodeName}}), err
}
func (s *{{.schema.ID}}Client) Update(o *{{.prefix}}{{.schema.CodeName}}) (*{{.prefix}}{{.schema.CodeName}}, error) {
obj, err := s.objectClient.Update(o.Name, o)
return obj.(*{{.prefix}}{{.schema.CodeName}}), err
}
func (s *{{.schema.ID}}Client) Delete(name string, options *metav1.DeleteOptions) error {
return s.objectClient.Delete(name, options)
}
func (s *{{.schema.ID}}Client) DeleteNamespaced(namespace, name string, options *metav1.DeleteOptions) error {
return s.objectClient.DeleteNamespaced(namespace, name, options)
}
func (s *{{.schema.ID}}Client) List(opts metav1.ListOptions) (*{{.schema.CodeName}}List, error) {
obj, err := s.objectClient.List(opts)
return obj.(*{{.schema.CodeName}}List), err
}
func (s *{{.schema.ID}}Client) Watch(opts metav1.ListOptions) (watch.Interface, error) {
return s.objectClient.Watch(opts)
}
// Patch applies the patch and returns the patched deployment.
func (s *{{.schema.ID}}Client) Patch(o *{{.prefix}}{{.schema.CodeName}}, data []byte, subresources ...string) (*{{.prefix}}{{.schema.CodeName}}, error) {
obj, err := s.objectClient.Patch(o.Name, o, data, subresources...)
return obj.(*{{.prefix}}{{.schema.CodeName}}), err
}
func (s *{{.schema.ID}}Client) DeleteCollection(deleteOpts *metav1.DeleteOptions, listOpts metav1.ListOptions) error {
return s.objectClient.DeleteCollection(deleteOpts, listOpts)
}
func (s *{{.schema.ID}}Client) AddHandler(name string, sync {{.schema.CodeName}}HandlerFunc) {
s.Controller().AddHandler(name, sync)
}
func (s *{{.schema.ID}}Client) AddLifecycle(name string, lifecycle {{.schema.CodeName}}Lifecycle) {
sync := New{{.schema.CodeName}}LifecycleAdapter(name, false, s, lifecycle)
s.AddHandler(name, sync)
}
func (s *{{.schema.ID}}Client) AddClusterScopedHandler(name, clusterName string, sync {{.schema.CodeName}}HandlerFunc) {
s.Controller().AddClusterScopedHandler(name, clusterName, sync)
}
func (s *{{.schema.ID}}Client) AddClusterScopedLifecycle(name, clusterName string, lifecycle {{.schema.CodeName}}Lifecycle) {
sync := New{{.schema.CodeName}}LifecycleAdapter(name+"_"+clusterName, true, s, lifecycle)
s.AddClusterScopedHandler(name, clusterName, sync)
}
`

View File

@@ -1,50 +0,0 @@
package generator
import (
"net/http"
"strings"
"text/template"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
)
func funcs() template.FuncMap {
return template.FuncMap{
"capitalize": convert.Capitalize,
"unCapitalize": convert.Uncapitalize,
"upper": strings.ToUpper,
"toLower": strings.ToLower,
"hasGet": hasGet,
"hasPost": hasPost,
"getCollectionOutput": getCollectionOutput,
}
}
func addUnderscore(input string) string {
return strings.ToLower(underscoreRegexp.ReplaceAllString(input, `${1}_${2}`))
}
func hasGet(schema *types.Schema) bool {
return contains(schema.CollectionMethods, http.MethodGet)
}
func hasPost(schema *types.Schema) bool {
return contains(schema.CollectionMethods, http.MethodPost)
}
func contains(list []string, needle string) bool {
for _, i := range list {
if i == needle {
return true
}
}
return false
}
func getCollectionOutput(output, codeName string) string {
if output == "collection" {
return codeName + "Collection"
}
return convert.Capitalize(output)
}

View File

@@ -1,526 +0,0 @@
package generator
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
"regexp"
"strings"
"text/template"
"github.com/pkg/errors"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"k8s.io/gengo/args"
"k8s.io/gengo/examples/deepcopy-gen/generators"
"k8s.io/gengo/generator"
gengotypes "k8s.io/gengo/types"
)
var (
blackListTypes = map[string]bool{
"schema": true,
"resource": true,
"collection": true,
}
underscoreRegexp = regexp.MustCompile(`([a-z])([A-Z])`)
)
type fieldInfo struct {
Name string
Type string
}
func getGoType(field types.Field, schema *types.Schema, schemas *types.Schemas) string {
return getTypeString(field.Nullable, field.Type, schema, schemas)
}
func getTypeString(nullable bool, typeName string, schema *types.Schema, schemas *types.Schemas) string {
switch {
case strings.HasPrefix(typeName, "reference["):
return "string"
case strings.HasPrefix(typeName, "map["):
return "map[string]" + getTypeString(false, typeName[len("map["):len(typeName)-1], schema, schemas)
case strings.HasPrefix(typeName, "array["):
return "[]" + getTypeString(false, typeName[len("array["):len(typeName)-1], schema, schemas)
}
name := ""
switch typeName {
case "base64":
return "string"
case "json":
return "interface{}"
case "boolean":
name = "bool"
case "float":
name = "float64"
case "int":
name = "int64"
case "multiline":
return "string"
case "masked":
return "string"
case "password":
return "string"
case "date":
return "string"
case "string":
return "string"
case "enum":
return "string"
case "intOrString":
return "intstr.IntOrString"
case "dnsLabel":
return "string"
case "dnsLabelRestricted":
return "string"
case "hostname":
return "string"
default:
if schema != nil && schemas != nil {
otherSchema := schemas.Schema(&schema.Version, typeName)
if otherSchema != nil {
name = otherSchema.CodeName
}
}
if name == "" {
name = convert.Capitalize(typeName)
}
}
if nullable {
return "*" + name
}
return name
}
func getTypeMap(schema *types.Schema, schemas *types.Schemas) map[string]fieldInfo {
result := map[string]fieldInfo{}
for name, field := range schema.ResourceFields {
if strings.EqualFold(name, "id") {
continue
}
result[field.CodeName] = fieldInfo{
Name: name,
Type: getGoType(field, schema, schemas),
}
}
return result
}
func getResourceActions(schema *types.Schema, schemas *types.Schemas) map[string]types.Action {
result := map[string]types.Action{}
for name, action := range schema.ResourceActions {
if action.Output != "" {
if schemas.Schema(&schema.Version, action.Output) != nil {
result[name] = action
}
} else {
result[name] = action
}
}
return result
}
func getCollectionActions(schema *types.Schema, schemas *types.Schemas) map[string]types.Action {
result := map[string]types.Action{}
for name, action := range schema.CollectionActions {
if action.Output != "" {
output := action.Output
if action.Output == "collection" {
output = strings.ToLower(schema.CodeName)
}
if schemas.Schema(&schema.Version, output) != nil {
result[name] = action
}
} else {
result[name] = action
}
}
return result
}
func generateType(outputDir string, schema *types.Schema, schemas *types.Schemas) error {
filePath := strings.ToLower("zz_generated_" + addUnderscore(schema.ID) + ".go")
output, err := os.Create(path.Join(outputDir, filePath))
if err != nil {
return err
}
defer output.Close()
typeTemplate, err := template.New("type.template").
Funcs(funcs()).
Parse(strings.Replace(typeTemplate, "%BACK%", "`", -1))
if err != nil {
return err
}
return typeTemplate.Execute(output, map[string]interface{}{
"schema": schema,
"structFields": getTypeMap(schema, schemas),
"resourceActions": getResourceActions(schema, schemas),
"collectionActions": getCollectionActions(schema, schemas),
})
}
func generateLifecycle(external bool, outputDir string, schema *types.Schema, schemas *types.Schemas) error {
filePath := strings.ToLower("zz_generated_" + addUnderscore(schema.ID) + "_lifecycle_adapter.go")
output, err := os.Create(path.Join(outputDir, filePath))
if err != nil {
return err
}
defer output.Close()
typeTemplate, err := template.New("lifecycle.template").
Funcs(funcs()).
Parse(strings.Replace(lifecycleTemplate, "%BACK%", "`", -1))
if err != nil {
return err
}
importPackage := ""
prefix := ""
if external {
parts := strings.Split(schema.PkgName, "/vendor/")
importPackage = fmt.Sprintf("\"%s\"", parts[len(parts)-1])
prefix = schema.Version.Version + "."
}
return typeTemplate.Execute(output, map[string]interface{}{
"schema": schema,
"importPackage": importPackage,
"prefix": prefix,
})
}
func generateController(external bool, outputDir string, schema *types.Schema, schemas *types.Schemas) error {
filePath := strings.ToLower("zz_generated_" + addUnderscore(schema.ID) + "_controller.go")
output, err := os.Create(path.Join(outputDir, filePath))
if err != nil {
return err
}
defer output.Close()
typeTemplate, err := template.New("controller.template").
Funcs(funcs()).
Parse(strings.Replace(controllerTemplate, "%BACK%", "`", -1))
if err != nil {
return err
}
importPackage := ""
prefix := ""
if external {
parts := strings.Split(schema.PkgName, "/vendor/")
importPackage = fmt.Sprintf("\"%s\"", parts[len(parts)-1])
prefix = schema.Version.Version + "."
}
return typeTemplate.Execute(output, map[string]interface{}{
"schema": schema,
"importPackage": importPackage,
"prefix": prefix,
})
}
func generateScheme(external bool, outputDir string, version *types.APIVersion, schemas []*types.Schema) error {
filePath := strings.ToLower("zz_generated_scheme.go")
output, err := os.Create(path.Join(outputDir, filePath))
if err != nil {
return err
}
defer output.Close()
typeTemplate, err := template.New("scheme.template").
Funcs(funcs()).
Parse(strings.Replace(schemeTemplate, "%BACK%", "`", -1))
if err != nil {
return err
}
names := []string{}
for _, schema := range schemas {
if !external {
names = append(names, schema.CodeName)
}
if schema.CanList(nil) == nil {
names = append(names, schema.CodeName+"List")
}
}
return typeTemplate.Execute(output, map[string]interface{}{
"version": version,
"schemas": schemas,
"names": names,
})
}
func generateK8sClient(outputDir string, version *types.APIVersion, schemas []*types.Schema) error {
filePath := strings.ToLower("zz_generated_k8s_client.go")
output, err := os.Create(path.Join(outputDir, filePath))
if err != nil {
return err
}
defer output.Close()
typeTemplate, err := template.New("k8sClient.template").
Funcs(funcs()).
Parse(strings.Replace(k8sClientTemplate, "%BACK%", "`", -1))
if err != nil {
return err
}
return typeTemplate.Execute(output, map[string]interface{}{
"version": version,
"schemas": schemas,
})
}
func generateClient(outputDir string, schemas []*types.Schema) error {
template, err := template.New("client.template").
Funcs(funcs()).
Parse(clientTemplate)
if err != nil {
return err
}
output, err := os.Create(path.Join(outputDir, "zz_generated_client.go"))
if err != nil {
return err
}
defer output.Close()
return template.Execute(output, map[string]interface{}{
"schemas": schemas,
})
}
func GenerateControllerForTypes(version *types.APIVersion, k8sOutputPackage string, nsObjs []interface{}, objs []interface{}) error {
baseDir := args.DefaultSourceTree()
k8sDir := path.Join(baseDir, k8sOutputPackage)
if err := prepareDirs(k8sDir); err != nil {
return err
}
schemas := types.NewSchemas()
var controllers []*types.Schema
for _, obj := range objs {
schema, err := schemas.Import(version, obj)
if err != nil {
return err
}
controllers = append(controllers, schema)
if err := generateController(true, k8sDir, schema, schemas); err != nil {
return err
}
if err := generateLifecycle(true, k8sDir, schema, schemas); err != nil {
return err
}
}
for _, obj := range nsObjs {
schema, err := schemas.Import(version, obj)
if err != nil {
return err
}
schema.Scope = types.NamespaceScope
controllers = append(controllers, schema)
if err := generateController(true, k8sDir, schema, schemas); err != nil {
return err
}
if err := generateLifecycle(true, k8sDir, schema, schemas); err != nil {
return err
}
}
if err := deepCopyGen(baseDir, k8sOutputPackage); err != nil {
return err
}
if err := generateK8sClient(k8sDir, version, controllers); err != nil {
return err
}
if err := generateScheme(true, k8sDir, version, controllers); err != nil {
return err
}
return gofmt(baseDir, k8sOutputPackage)
}
func Generate(schemas *types.Schemas, backendTypes map[string]bool, cattleOutputPackage, k8sOutputPackage string) error {
baseDir := args.DefaultSourceTree()
cattleDir := path.Join(baseDir, cattleOutputPackage)
k8sDir := path.Join(baseDir, k8sOutputPackage)
if err := prepareDirs(cattleDir, k8sDir); err != nil {
return err
}
controllers := []*types.Schema{}
cattleClientTypes := []*types.Schema{}
for _, schema := range schemas.Schemas() {
if blackListTypes[schema.ID] {
continue
}
_, backendType := backendTypes[schema.ID]
if err := generateType(cattleDir, schema, schemas); err != nil {
return err
}
if backendType ||
(contains(schema.CollectionMethods, http.MethodGet) &&
!strings.HasPrefix(schema.PkgName, "k8s.io") &&
!strings.Contains(schema.PkgName, "/vendor/")) {
controllers = append(controllers, schema)
if err := generateController(false, k8sDir, schema, schemas); err != nil {
return err
}
if err := generateLifecycle(false, k8sDir, schema, schemas); err != nil {
return err
}
}
if !backendType {
cattleClientTypes = append(cattleClientTypes, schema)
}
}
if err := generateClient(cattleDir, cattleClientTypes); err != nil {
return err
}
if len(controllers) > 0 {
if err := deepCopyGen(baseDir, k8sOutputPackage); err != nil {
return err
}
if err := generateK8sClient(k8sDir, &controllers[0].Version, controllers); err != nil {
return err
}
if err := generateScheme(false, k8sDir, &controllers[0].Version, controllers); err != nil {
return err
}
}
if err := gofmt(baseDir, k8sOutputPackage); err != nil {
return err
}
return gofmt(baseDir, cattleOutputPackage)
}
func prepareDirs(dirs ...string) error {
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
files, err := ioutil.ReadDir(dir)
if err != nil {
return err
}
for _, file := range files {
if strings.HasPrefix(file.Name(), "zz_generated") {
if err := os.Remove(path.Join(dir, file.Name())); err != nil {
return errors.Wrapf(err, "failed to delete %s", path.Join(dir, file.Name()))
}
}
}
}
return nil
}
func gofmt(workDir, pkg string) error {
cmd := exec.Command("goimports", "-w", "-l", "./"+pkg)
cmd.Dir = workDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func deepCopyGen(workDir, pkg string) error {
arguments := &args.GeneratorArgs{
InputDirs: []string{pkg},
OutputBase: workDir,
OutputPackagePath: pkg,
OutputFileBaseName: "zz_generated_deepcopy",
GoHeaderFilePath: "/dev/null",
GeneratedBuildTag: "ignore_autogenerated",
}
return arguments.Execute(
generators.NameSystems(),
generators.DefaultNameSystem(),
func(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
packageParts := strings.Split(pkg, "/")
return generator.Packages{
&generator.DefaultPackage{
PackageName: packageParts[len(packageParts)-1],
PackagePath: pkg,
HeaderText: []byte{},
GeneratorFunc: func(c *generator.Context) []generator.Generator {
return []generator.Generator{
//&noInitGenerator{
generators.NewGenDeepCopy(arguments.OutputFileBaseName, pkg, nil, true, true),
//},
}
},
FilterFunc: func(c *generator.Context, t *gengotypes.Type) bool {
if t.Name.Package != pkg {
return false
}
if isObjectOrList(t) {
t.SecondClosestCommentLines = append(t.SecondClosestCommentLines,
"+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object")
}
return true
},
},
}
})
}
type noInitGenerator struct {
generator.Generator
}
func (n *noInitGenerator) Init(*generator.Context, io.Writer) error {
return nil
}
func isObjectOrList(t *gengotypes.Type) bool {
for _, member := range t.Members {
if member.Embedded && (member.Name == "ObjectMeta" || member.Name == "ListMeta") {
return true
}
if member.Embedded && isObjectOrList(member.Type) {
return true
}
}
return false
}

View File

@@ -1,74 +0,0 @@
package generator
var k8sClientTemplate = `package {{.version.Version}}
import (
"sync"
"context"
"github.com/rancher/norman/objectclient"
"github.com/rancher/norman/objectclient/dynamic"
"github.com/rancher/norman/controller"
"github.com/rancher/norman/restwatch"
"k8s.io/client-go/rest"
)
type Interface interface {
RESTClient() rest.Interface
controller.Starter
{{range .schemas}}
{{.CodeNamePlural}}Getter{{end}}
}
type Client struct {
sync.Mutex
restClient rest.Interface
starters []controller.Starter
{{range .schemas}}
{{.ID}}Controllers map[string]{{.CodeName}}Controller{{end}}
}
func NewForConfig(config rest.Config) (Interface, error) {
if config.NegotiatedSerializer == nil {
config.NegotiatedSerializer = dynamic.NegotiatedSerializer
}
restClient, err := restwatch.UnversionedRESTClientFor(&config)
if err != nil {
return nil, err
}
return &Client{
restClient: restClient,
{{range .schemas}}
{{.ID}}Controllers: map[string]{{.CodeName}}Controller{},{{end}}
}, nil
}
func (c *Client) RESTClient() rest.Interface {
return c.restClient
}
func (c *Client) Sync(ctx context.Context) error {
return controller.Sync(ctx, c.starters...)
}
func (c *Client) Start(ctx context.Context, threadiness int) error {
return controller.Start(ctx, threadiness, c.starters...)
}
{{range .schemas}}
type {{.CodeNamePlural}}Getter interface {
{{.CodeNamePlural}}(namespace string) {{.CodeName}}Interface
}
func (c *Client) {{.CodeNamePlural}}(namespace string) {{.CodeName}}Interface {
objectClient := objectclient.NewObjectClient(namespace, c.restClient, &{{.CodeName}}Resource, {{.CodeName}}GroupVersionKind, {{.ID}}Factory{})
return &{{.ID}}Client{
ns: namespace,
client: c,
objectClient: objectClient,
}
}
{{end}}
`

View File

@@ -1,55 +0,0 @@
package generator
var lifecycleTemplate = `package {{.schema.Version.Version}}
import (
{{.importPackage}}
"k8s.io/apimachinery/pkg/runtime"
"github.com/rancher/norman/lifecycle"
)
type {{.schema.CodeName}}Lifecycle interface {
Create(obj *{{.prefix}}{{.schema.CodeName}}) (*{{.prefix}}{{.schema.CodeName}}, error)
Remove(obj *{{.prefix}}{{.schema.CodeName}}) (*{{.prefix}}{{.schema.CodeName}}, error)
Updated(obj *{{.prefix}}{{.schema.CodeName}}) (*{{.prefix}}{{.schema.CodeName}}, error)
}
type {{.schema.ID}}LifecycleAdapter struct {
lifecycle {{.schema.CodeName}}Lifecycle
}
func (w *{{.schema.ID}}LifecycleAdapter) Create(obj runtime.Object) (runtime.Object, error) {
o, err := w.lifecycle.Create(obj.(*{{.prefix}}{{.schema.CodeName}}))
if o == nil {
return nil, err
}
return o, err
}
func (w *{{.schema.ID}}LifecycleAdapter) Finalize(obj runtime.Object) (runtime.Object, error) {
o, err := w.lifecycle.Remove(obj.(*{{.prefix}}{{.schema.CodeName}}))
if o == nil {
return nil, err
}
return o, err
}
func (w *{{.schema.ID}}LifecycleAdapter) Updated(obj runtime.Object) (runtime.Object, error) {
o, err := w.lifecycle.Updated(obj.(*{{.prefix}}{{.schema.CodeName}}))
if o == nil {
return nil, err
}
return o, err
}
func New{{.schema.CodeName}}LifecycleAdapter(name string, clusterScoped bool, client {{.schema.CodeName}}Interface, l {{.schema.CodeName}}Lifecycle) {{.schema.CodeName}}HandlerFunc {
adapter := &{{.schema.ID}}LifecycleAdapter{lifecycle: l}
syncFn := lifecycle.NewObjectLifecycleAdapter(name, clusterScoped, adapter, client.ObjectClient())
return func(key string, obj *{{.prefix}}{{.schema.CodeName}}) error {
if obj == nil {
return syncFn(key, nil)
}
return syncFn(key, obj)
}
}
`

View File

@@ -1,42 +0,0 @@
package generator
var schemeTemplate = `package {{.version.Version}}
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
const (
GroupName = "{{.version.Group}}"
Version = "{{.version.Version}}"
)
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: Version}
// Kind takes an unqualified kind and returns a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
// Adds the list of known types to api.Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
// TODO this gets cleaned up when the types are fixed
scheme.AddKnownTypes(SchemeGroupVersion,
{{range .names}}
&{{.}}{},{{end}}
)
return nil
}
`

View File

@@ -1,166 +0,0 @@
package generator
var typeTemplate = `package client
{{- if .schema | hasGet }}
import (
"github.com/rancher/norman/types"
"k8s.io/apimachinery/pkg/util/intstr"
)
{{- end}}
const (
{{.schema.CodeName}}Type = "{{.schema.ID}}"
{{- range $key, $value := .structFields}}
{{$.schema.CodeName}}Field{{$key}} = "{{$value.Name}}"
{{- end}}
)
type {{.schema.CodeName}} struct {
{{- if .schema | hasGet }}
types.Resource
{{- end}}
{{- range $key, $value := .structFields}}
{{$key}} {{$value.Type}} %BACK%json:"{{$value.Name}},omitempty" yaml:"{{$value.Name}},omitempty"%BACK%
{{- end}}
}
{{ if .schema | hasGet }}
type {{.schema.CodeName}}Collection struct {
types.Collection
Data []{{.schema.CodeName}} %BACK%json:"data,omitempty"%BACK%
client *{{.schema.CodeName}}Client
}
type {{.schema.CodeName}}Client struct {
apiClient *Client
}
type {{.schema.CodeName}}Operations interface {
List(opts *types.ListOpts) (*{{.schema.CodeName}}Collection, error)
Create(opts *{{.schema.CodeName}}) (*{{.schema.CodeName}}, error)
Update(existing *{{.schema.CodeName}}, updates interface{}) (*{{.schema.CodeName}}, error)
Replace(existing *{{.schema.CodeName}}) (*{{.schema.CodeName}}, error)
ByID(id string) (*{{.schema.CodeName}}, error)
Delete(container *{{.schema.CodeName}}) error
{{range $key, $value := .resourceActions}}
{{if (and (eq $value.Input "") (eq $value.Output ""))}}
Action{{$key | capitalize}} (resource *{{$.schema.CodeName}}) (error)
{{else if (and (eq $value.Input "") (ne $value.Output ""))}}
Action{{$key | capitalize}} (resource *{{$.schema.CodeName}}) (*{{.Output | capitalize}}, error)
{{else if (and (ne $value.Input "") (eq $value.Output ""))}}
Action{{$key | capitalize}} (resource *{{$.schema.CodeName}}, input *{{$value.Input | capitalize}}) (error)
{{else}}
Action{{$key | capitalize}} (resource *{{$.schema.CodeName}}, input *{{$value.Input | capitalize}}) (*{{.Output | capitalize}}, error)
{{end}}
{{end}}
{{range $key, $value := .collectionActions}}
{{if (and (eq $value.Input "") (eq $value.Output ""))}}
CollectionAction{{$key | capitalize}} (resource *{{$.schema.CodeName}}Collection) (error)
{{else if (and (eq $value.Input "") (ne $value.Output ""))}}
CollectionAction{{$key | capitalize}} (resource *{{$.schema.CodeName}}Collection) (*{{getCollectionOutput $value.Output $.schema.CodeName}}, error)
{{else if (and (ne $value.Input "") (eq $value.Output ""))}}
CollectionAction{{$key | capitalize}} (resource *{{$.schema.CodeName}}Collection, input *{{$value.Input | capitalize}}) (error)
{{else}}
CollectionAction{{$key | capitalize}} (resource *{{$.schema.CodeName}}Collection, input *{{$value.Input | capitalize}}) (*{{getCollectionOutput $value.Output $.schema.CodeName}}, error)
{{end}}
{{end}}
}
func new{{.schema.CodeName}}Client(apiClient *Client) *{{.schema.CodeName}}Client {
return &{{.schema.CodeName}}Client{
apiClient: apiClient,
}
}
func (c *{{.schema.CodeName}}Client) Create(container *{{.schema.CodeName}}) (*{{.schema.CodeName}}, error) {
resp := &{{.schema.CodeName}}{}
err := c.apiClient.Ops.DoCreate({{.schema.CodeName}}Type, container, resp)
return resp, err
}
func (c *{{.schema.CodeName}}Client) Update(existing *{{.schema.CodeName}}, updates interface{}) (*{{.schema.CodeName}}, error) {
resp := &{{.schema.CodeName}}{}
err := c.apiClient.Ops.DoUpdate({{.schema.CodeName}}Type, &existing.Resource, updates, resp)
return resp, err
}
func (c *{{.schema.CodeName}}Client) Replace(obj *{{.schema.CodeName}}) (*{{.schema.CodeName}}, error) {
resp := &{{.schema.CodeName}}{}
err := c.apiClient.Ops.DoReplace({{.schema.CodeName}}Type, &obj.Resource, obj, resp)
return resp, err
}
func (c *{{.schema.CodeName}}Client) List(opts *types.ListOpts) (*{{.schema.CodeName}}Collection, error) {
resp := &{{.schema.CodeName}}Collection{}
err := c.apiClient.Ops.DoList({{.schema.CodeName}}Type, opts, resp)
resp.client = c
return resp, err
}
func (cc *{{.schema.CodeName}}Collection) Next() (*{{.schema.CodeName}}Collection, error) {
if cc != nil && cc.Pagination != nil && cc.Pagination.Next != "" {
resp := &{{.schema.CodeName}}Collection{}
err := cc.client.apiClient.Ops.DoNext(cc.Pagination.Next, resp)
resp.client = cc.client
return resp, err
}
return nil, nil
}
func (c *{{.schema.CodeName}}Client) ByID(id string) (*{{.schema.CodeName}}, error) {
resp := &{{.schema.CodeName}}{}
err := c.apiClient.Ops.DoByID({{.schema.CodeName}}Type, id, resp)
return resp, err
}
func (c *{{.schema.CodeName}}Client) Delete(container *{{.schema.CodeName}}) error {
return c.apiClient.Ops.DoResourceDelete({{.schema.CodeName}}Type, &container.Resource)
}
{{range $key, $value := .resourceActions}}
{{if (and (eq $value.Input "") (eq $value.Output ""))}}
func (c *{{$.schema.CodeName}}Client) Action{{$key | capitalize}} (resource *{{$.schema.CodeName}}) (error) {
err := c.apiClient.Ops.DoAction({{$.schema.CodeName}}Type, "{{$key}}", &resource.Resource, nil, nil)
return err
{{else if (and (eq $value.Input "") (ne $value.Output ""))}}
func (c *{{$.schema.CodeName}}Client) Action{{$key | capitalize}} (resource *{{$.schema.CodeName}}) (*{{.Output | capitalize}}, error) {
resp := &{{.Output | capitalize}}{}
err := c.apiClient.Ops.DoAction({{$.schema.CodeName}}Type, "{{$key}}", &resource.Resource, nil, resp)
return resp, err
{{else if (and (ne $value.Input "") (eq $value.Output ""))}}
func (c *{{$.schema.CodeName}}Client) Action{{$key | capitalize}} (resource *{{$.schema.CodeName}}, input *{{$value.Input | capitalize}}) (error) {
err := c.apiClient.Ops.DoAction({{$.schema.CodeName}}Type, "{{$key}}", &resource.Resource, input, nil)
return err
{{else}}
func (c *{{$.schema.CodeName}}Client) Action{{$key | capitalize}} (resource *{{$.schema.CodeName}}, input *{{$value.Input | capitalize}}) (*{{.Output | capitalize}}, error) {
resp := &{{.Output | capitalize}}{}
err := c.apiClient.Ops.DoAction({{$.schema.CodeName}}Type, "{{$key}}", &resource.Resource, input, resp)
return resp, err
{{- end -}}
}
{{end}}
{{range $key, $value := .collectionActions}}
{{if (and (eq $value.Input "") (eq $value.Output ""))}}
func (c *{{$.schema.CodeName}}Client) CollectionAction{{$key | capitalize}} (resource *{{$.schema.CodeName}}Collection) (error) {
err := c.apiClient.Ops.DoCollectionAction({{$.schema.CodeName}}Type, "{{$key}}", &resource.Collection, nil, nil)
return err
{{else if (and (eq $value.Input "") (ne $value.Output ""))}}
func (c *{{$.schema.CodeName}}Client) CollectionAction{{$key | capitalize}} (resource *{{$.schema.CodeName}}Collection) (*{{getCollectionOutput $value.Output $.schema.CodeName}}, error) {
resp := &{{getCollectionOutput $value.Output $.schema.CodeName}}{}
err := c.apiClient.Ops.DoCollectionAction({{$.schema.CodeName}}Type, "{{$key}}", &resource.Collection, nil, resp)
return resp, err
{{else if (and (ne $value.Input "") (eq $value.Output ""))}}
func (c *{{$.schema.CodeName}}Client) CollectionAction{{$key | capitalize}} (resource *{{$.schema.CodeName}}Collection, input *{{$value.Input | capitalize}}) (error) {
err := c.apiClient.Ops.DoCollectionAction({{$.schema.CodeName}}Type, "{{$key}}", &resource.Collection, input, nil)
return err
{{else}}
func (c *{{$.schema.CodeName}}Client) CollectionAction{{$key | capitalize}} (resource *{{$.schema.CodeName}}Collection, input *{{$value.Input | capitalize}}) (*{{getCollectionOutput $value.Output $.schema.CodeName}}, error) {
resp := &{{getCollectionOutput $value.Output $.schema.CodeName}}{}
err := c.apiClient.Ops.DoCollectionAction({{$.schema.CodeName}}Type, "{{$key}}", &resource.Collection, input, resp)
return resp, err
{{- end -}}
}
{{end}}
{{end}}`

View File

@@ -1,55 +0,0 @@
module github.com/rancher/norman
require (
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
github.com/davecgh/go-spew v1.1.1-0.20170626231645-782f4967f2dc // indirect
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680
github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e // indirect
github.com/golang/glog v0.0.0-20141105023935-44145f04b68c // indirect
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 // indirect
github.com/golang/protobuf v1.2.0 // indirect
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367 // indirect
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d // indirect
github.com/gorilla/websocket v0.0.0-20150714140627-6eb6ad425a89
github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880 // indirect
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c // indirect
github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1 // indirect
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3 // indirect
github.com/kr/pretty v0.0.0-20140812000539-f31442d60e51 // indirect
github.com/kr/text v0.0.0-20130911015532-6807e777504f // indirect
github.com/maruel/panicparse v1.1.1
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da // indirect
github.com/onsi/ginkgo v1.2.1-0.20170318221715-67b9df7f55fe // indirect
github.com/onsi/gomega v0.0.0-20160911051023-d59fa0ac68bb // indirect
github.com/pkg/errors v0.8.0
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 // indirect
github.com/prometheus/client_golang v0.8.0
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7 // indirect
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2
github.com/spf13/pflag v1.0.1 // indirect
github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680
golang.org/x/crypto v0.0.0-20170825220121-81e90905daef // indirect
golang.org/x/net v0.0.0-20170809000501-1c05540f6879 // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
golang.org/x/sys v0.0.0-20171031081856-95c657629925 // indirect
golang.org/x/text v0.0.0-20170810154203-b19bf474d317 // indirect
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d
golang.org/x/tools v0.0.0-20170428054726-2382e3994d48 // indirect
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
gopkg.in/inf.v0 v0.9.0 // indirect
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
gopkg.in/yaml.v2 v2.0.0-20170721113624-670d4cfef054 // indirect
k8s.io/api v0.0.0-20180621150657-6c0bbc3e58fa
k8s.io/apiextensions-apiserver v0.0.0-20180621165922-80db67131e8d
k8s.io/apimachinery v0.0.0-20180619225948-e386b2658ed2
k8s.io/client-go v2.0.0-alpha.0.0.20180621152933-b0722d92a7c1+incompatible
k8s.io/gengo v0.0.0-20180223161844-01a732e01d00
k8s.io/kube-openapi v0.0.0-20180509051136-39cb288412c4 // indirect
k8s.io/kubernetes v1.10.5
)

View File

@@ -1,104 +0,0 @@
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/davecgh/go-spew v1.1.1-0.20170626231645-782f4967f2dc h1:NlbIJbqL8zjb55Vdrsr5uqyVC6/NoUUd2YrLojfE2zI=
github.com/davecgh/go-spew v1.1.1-0.20170626231645-782f4967f2dc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7iEV1zPCGQldM2atlJZ3TdvVM=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e h1:ago6fNuQ6IhszPsXkeU7qRCyfsIX7L67WDybsAPkLl8=
github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20141105023935-44145f04b68c h1:CbdkBQ1/PiAo0FYJhQGwASD8wrgNvTdf01g6+O9tNuA=
github.com/golang/glog v0.0.0-20141105023935-44145f04b68c/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v0.0.0-20171021043952-1643683e1b54 h1:nRNJXiJvemchkOTn0V4U11TZkvacB94gTzbTZbSA7Rw=
github.com/golang/protobuf v0.0.0-20171021043952-1643683e1b54/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367 h1:ScAXWS+TR6MZKex+7Z8rneuSJH+FSDqd6ocQyl+ZHo4=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gorilla/websocket v0.0.0-20150714140627-6eb6ad425a89 h1:f3M+RTnIGEhCF8ynRezzgqxlQ+VBfer6kL61+4/W+v4=
github.com/gorilla/websocket v0.0.0-20150714140627-6eb6ad425a89/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880 h1:OaRuzt9oCKNui8cCskZijoKUwe+aCuuCwvx1ox8FNyw=
github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c h1:kQWxfPIHVLbgLzphqk3QUflDy9QdksZR4ygR807bpy0=
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1 h1:FeeCi0I2Fu8kA8IXrdVPtGzym+mW9bzfj9f26EaES9k=
github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3 h1:/UewZcckqhvnnS0C6r3Sher2hSEbVmM6Ogpcjen08+Y=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kr/pretty v0.0.0-20140812000539-f31442d60e51 h1:kGEU5h0EzkNa+B8Q3e0GlaIocJYB1G6ZpefcceXhfgc=
github.com/kr/pretty v0.0.0-20140812000539-f31442d60e51/go.mod h1:Bvhd+E3laJ0AVkG0c9rmtZcnhV0HQ3+c3YxxqTvc/gA=
github.com/kr/text v0.0.0-20130911015532-6807e777504f h1:JaNmHIV9Eby6srQVWuiQ6n8ko2o/lG6udSRCbFZe1fs=
github.com/kr/text v0.0.0-20130911015532-6807e777504f/go.mod h1:sjUstKUATFIcff4qlB53Kml0wQPtJVc/3fWrmuUmcfA=
github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5IkRc=
github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da h1:ZQGIPjr1iTtUPXZFk8WShqb5G+Qg65VHFLtSvmHh+Mw=
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/onsi/ginkgo v1.2.1-0.20170318221715-67b9df7f55fe h1:d3gNxYlRvgsR9X/YxcYc0e0wsFAhC6u5zM51TC+o+EA=
github.com/onsi/ginkgo v1.2.1-0.20170318221715-67b9df7f55fe/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20160911051023-d59fa0ac68bb h1:myDTJUQm/UVMeOHuw47rGP+3Id5b0s0T7EVl71ZweuI=
github.com/onsi/gomega v0.0.0-20160911051023-d59fa0ac68bb/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 h1:GD+A8+e+wFkqje55/2fOVnZPkoDIu1VooBWfNrnY8Uo=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7 h1:NgR6WN8nQ4SmFC1sSUHY8SriLuWCZ6cCIQtH4vDZN3c=
github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2 h1:a07zp0wovcAE2jH+wlD22JLqUH6Rdl8Aon+NiyPxE+0=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680 h1:oAXco1Ts88F75L1qvG3BAa4ChXI3EZDfxbB+p+y8+gE=
github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20170825220121-81e90905daef h1:R8ubLIilYRXIXpgjOg2l/ECVs3HzVKIjJEhxSsQ91u4=
golang.org/x/crypto v0.0.0-20170825220121-81e90905daef/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20170809000501-1c05540f6879 h1:0rFa7EaCGdQPmZVbo9F7MNF65b8dyzS6EUnXjs9Cllk=
golang.org/x/net v0.0.0-20170809000501-1c05540f6879/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20171031081856-95c657629925 h1:nCH33NboKIsT4HoXBsXTWX8ul303HxWgkc5s2Ezwacg=
golang.org/x/sys v0.0.0-20171031081856-95c657629925/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.0.0-20170810154203-b19bf474d317 h1:WKW+OPdYPlvOTVGHuMfjnIC6yY2SI93yFB0pZ7giBmQ=
golang.org/x/text v0.0.0-20170810154203-b19bf474d317/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20170428054726-2382e3994d48 h1:Al/HKLBwsMBsWhxa71LOWO8MeCbD21L+x5rHb83JHjI=
golang.org/x/tools v0.0.0-20170428054726-2382e3994d48/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.0.0-20170721113624-670d4cfef054 h1:ROF+R/wHHruzF40n5DfPv2jwm7rCJwvs8fz+RTZWjLE=
gopkg.in/yaml.v2 v2.0.0-20170721113624-670d4cfef054/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
k8s.io/api v0.0.0-20180621150657-6c0bbc3e58fa h1:FdiZyyrmQXY7AWCNUfAJrx9UCjMF/oBNZP8CmKoc2aU=
k8s.io/api v0.0.0-20180621150657-6c0bbc3e58fa/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/apiextensions-apiserver v0.0.0-20180621165922-80db67131e8d h1:QYxqxjF8LG0fEp4lhpDj4zGXaO1EAbt97/3vqVL0dpk=
k8s.io/apiextensions-apiserver v0.0.0-20180621165922-80db67131e8d/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
k8s.io/apimachinery v0.0.0-20180619225948-e386b2658ed2 h1:NJEj7o7SKxpURej3uJ1QZJZCeRlRj21EatnCK65nrB4=
k8s.io/apimachinery v0.0.0-20180619225948-e386b2658ed2/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/client-go v2.0.0-alpha.0.0.20180621152933-b0722d92a7c1+incompatible h1:lph8g2o3QoQdw5W+fKHD/+Td4MEN2dmXgAjoOH5aISo=
k8s.io/client-go v2.0.0-alpha.0.0.20180621152933-b0722d92a7c1+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/gengo v0.0.0-20180223161844-01a732e01d00 h1:vt4Sh/+HFnLoTScgFLNoMjNqOg0sQgAzViarcz+UX3Q=
k8s.io/gengo v0.0.0-20180223161844-01a732e01d00/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/kube-openapi v0.0.0-20180509051136-39cb288412c4 h1:gW+EUB2I96nbxVenV/8ctfbACsHP+yxlT2dhMCsiy+s=
k8s.io/kube-openapi v0.0.0-20180509051136-39cb288412c4/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/kubernetes v1.10.5 h1:buoMLO5r3BpYzUebBdwxa67P2evcpmaydiiKMLq9K1U=
k8s.io/kubernetes v1.10.5/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=

View File

@@ -1,114 +0,0 @@
package httperror
import (
"fmt"
)
var (
Unauthorized = ErrorCode{"Unauthorized", 401}
PermissionDenied = ErrorCode{"PermissionDenied", 403}
NotFound = ErrorCode{"NotFound", 404}
MethodNotAllowed = ErrorCode{"MethodNotAllow", 405}
Conflict = ErrorCode{"Conflict", 409}
InvalidDateFormat = ErrorCode{"InvalidDateFormat", 422}
InvalidFormat = ErrorCode{"InvalidFormat", 422}
InvalidReference = ErrorCode{"InvalidReference", 422}
NotNullable = ErrorCode{"NotNullable", 422}
NotUnique = ErrorCode{"NotUnique", 422}
MinLimitExceeded = ErrorCode{"MinLimitExceeded", 422}
MaxLimitExceeded = ErrorCode{"MaxLimitExceeded", 422}
MinLengthExceeded = ErrorCode{"MinLengthExceeded", 422}
MaxLengthExceeded = ErrorCode{"MaxLengthExceeded", 422}
InvalidOption = ErrorCode{"InvalidOption", 422}
InvalidCharacters = ErrorCode{"InvalidCharacters", 422}
MissingRequired = ErrorCode{"MissingRequired", 422}
InvalidCSRFToken = ErrorCode{"InvalidCSRFToken", 422}
InvalidAction = ErrorCode{"InvalidAction", 422}
InvalidBodyContent = ErrorCode{"InvalidBodyContent", 422}
InvalidType = ErrorCode{"InvalidType", 422}
ActionNotAvailable = ErrorCode{"ActionNotAvailable", 404}
InvalidState = ErrorCode{"InvalidState", 422}
ServerError = ErrorCode{"ServerError", 500}
ClusterUnavailable = ErrorCode{"ClusterUnavailable", 503}
)
type ErrorCode struct {
Code string
Status int
}
func (e ErrorCode) String() string {
return fmt.Sprintf("%s %d", e.Code, e.Status)
}
type APIError struct {
Code ErrorCode
Message string
Cause error
FieldName string
}
func NewAPIErrorLong(status int, code, message string) error {
return NewAPIError(ErrorCode{
Code: code,
Status: status,
}, message)
}
func NewAPIError(code ErrorCode, message string) error {
return &APIError{
Code: code,
Message: message,
}
}
func NewFieldAPIError(code ErrorCode, fieldName, message string) error {
return &APIError{
Code: code,
Message: message,
FieldName: fieldName,
}
}
// WrapFieldAPIError will cause the API framework to log the underlying err before returning the APIError as a response.
// err WILL NOT be in the API response
func WrapFieldAPIError(err error, code ErrorCode, fieldName, message string) error {
return &APIError{
Cause: err,
Code: code,
Message: message,
FieldName: fieldName,
}
}
// WrapAPIError will cause the API framework to log the underlying err before returning the APIError as a response.
// err WILL NOT be in the API response
func WrapAPIError(err error, code ErrorCode, message string) error {
return &APIError{
Code: code,
Message: message,
Cause: err,
}
}
func (a *APIError) Error() string {
if a.FieldName != "" {
return fmt.Sprintf("%s=%s: %s", a.FieldName, a.Code, a.Message)
}
return fmt.Sprintf("%s: %s", a.Code, a.Message)
}
func IsAPIError(err error) bool {
_, ok := err.(*APIError)
return ok
}
func IsConflict(err error) bool {
if apiError, ok := err.(*APIError); ok {
return apiError.Code.Status == 409
}
return false
}

View File

@@ -1,47 +0,0 @@
package handler
import (
"net/url"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"github.com/sirupsen/logrus"
)
func ErrorHandler(request *types.APIContext, err error) {
var error *httperror.APIError
if apiError, ok := err.(*httperror.APIError); ok {
if apiError.Cause != nil {
url, _ := url.PathUnescape(request.Request.URL.String())
if url == "" {
url = request.Request.URL.String()
}
logrus.Errorf("API error response %v for %v %v. Cause: %v", apiError.Code.Status, request.Request.Method,
url, apiError.Cause)
}
error = apiError
} else {
logrus.Errorf("Unknown error: %v", err)
error = &httperror.APIError{
Code: httperror.ServerError,
Message: err.Error(),
}
}
data := toError(error)
request.WriteResponse(error.Code.Status, data)
}
func toError(apiError *httperror.APIError) map[string]interface{} {
e := map[string]interface{}{
"type": "/meta/schemas/error",
"status": apiError.Code.Status,
"code": apiError.Code.Code,
"message": apiError.Message,
}
if apiError.FieldName != "" {
e["fieldName"] = apiError.FieldName
}
return e
}

View File

@@ -1,77 +0,0 @@
package leader
import (
"context"
"os"
"github.com/sirupsen/logrus"
"k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/leaderelection"
"k8s.io/client-go/tools/leaderelection/resourcelock"
"k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/client/leaderelectionconfig"
)
type Callback func(cb context.Context)
func RunOrDie(ctx context.Context, name string, client kubernetes.Interface, cb Callback) {
err := run(ctx, name, client, cb)
if err != nil {
logrus.Fatalf("Failed to start leader election for %s", name)
}
panic("Failed to start leader election for " + name)
}
func run(ctx context.Context, name string, client kubernetes.Interface, cb Callback) error {
id, err := os.Hostname()
if err != nil {
return err
}
le := leaderelectionconfig.DefaultLeaderElectionConfiguration()
le.LeaderElect = true
le.ResourceLock = resourcelock.ConfigMapsResourceLock
recorder := createRecorder(name, client)
rl, err := resourcelock.New(le.ResourceLock,
"kube-system",
name,
client.CoreV1(),
resourcelock.ResourceLockConfig{
Identity: id,
EventRecorder: recorder,
})
if err != nil {
logrus.Fatalf("error creating leader lock for %s: %v", name, err)
}
leaderelection.RunOrDie(leaderelection.LeaderElectionConfig{
Lock: rl,
LeaseDuration: le.LeaseDuration.Duration,
RenewDeadline: le.RenewDeadline.Duration,
RetryPeriod: le.RetryPeriod.Duration,
Callbacks: leaderelection.LeaderCallbacks{
OnStartedLeading: func(stop <-chan struct{}) {
subCtx, cancel := context.WithCancel(ctx)
go cb(subCtx)
<-stop
cancel()
},
OnStoppedLeading: func() {
logrus.Fatalf("leaderelection lost for %s", name)
},
},
})
panic("unreachable")
}
func createRecorder(name string, kubeClient kubernetes.Interface) record.EventRecorder {
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(logrus.Debugf)
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")})
return eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: name})
}

View File

@@ -1,195 +0,0 @@
package lifecycle
import (
"fmt"
"reflect"
"github.com/rancher/norman/objectclient"
"github.com/rancher/norman/types/slice"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
var (
created = "lifecycle.cattle.io/create"
finalizerKey = "controller.cattle.io/"
ScopedFinalizerKey = "clusterscoped.controller.cattle.io/"
)
type ObjectLifecycle interface {
Create(obj runtime.Object) (runtime.Object, error)
Finalize(obj runtime.Object) (runtime.Object, error)
Updated(obj runtime.Object) (runtime.Object, error)
}
type objectLifecycleAdapter struct {
name string
clusterScoped bool
lifecycle ObjectLifecycle
objectClient *objectclient.ObjectClient
}
func NewObjectLifecycleAdapter(name string, clusterScoped bool, lifecycle ObjectLifecycle, objectClient *objectclient.ObjectClient) func(key string, obj runtime.Object) error {
o := objectLifecycleAdapter{
name: name,
clusterScoped: clusterScoped,
lifecycle: lifecycle,
objectClient: objectClient,
}
return o.sync
}
func (o *objectLifecycleAdapter) sync(key string, obj runtime.Object) error {
if obj == nil {
return nil
}
metadata, err := meta.Accessor(obj)
if err != nil {
return err
}
if cont, err := o.finalize(metadata, obj); err != nil || !cont {
return err
}
if cont, err := o.create(metadata, obj); err != nil || !cont {
return err
}
copyObj := obj.DeepCopyObject()
newObj, err := o.lifecycle.Updated(copyObj)
if newObj != nil {
o.update(metadata.GetName(), obj, newObj)
}
return err
}
func (o *objectLifecycleAdapter) update(name string, orig, obj runtime.Object) (runtime.Object, error) {
if obj != nil && !reflect.DeepEqual(orig, obj) {
return o.objectClient.Update(name, obj)
}
return obj, nil
}
func (o *objectLifecycleAdapter) finalize(metadata metav1.Object, obj runtime.Object) (bool, error) {
// Check finalize
if metadata.GetDeletionTimestamp() == nil {
return true, nil
}
if !slice.ContainsString(metadata.GetFinalizers(), o.constructFinalizerKey()) {
return false, nil
}
copyObj := obj.DeepCopyObject()
if newObj, err := o.lifecycle.Finalize(copyObj); err != nil {
if newObj != nil {
o.update(metadata.GetName(), obj, newObj)
}
return false, err
} else if newObj != nil {
copyObj = newObj
}
return false, o.removeFinalizer(o.constructFinalizerKey(), copyObj)
}
func (o *objectLifecycleAdapter) removeFinalizer(name string, obj runtime.Object) error {
for i := 0; i < 3; i++ {
metadata, err := meta.Accessor(obj)
if err != nil {
return err
}
var finalizers []string
for _, finalizer := range metadata.GetFinalizers() {
if finalizer == name {
continue
}
finalizers = append(finalizers, finalizer)
}
metadata.SetFinalizers(finalizers)
_, err = o.objectClient.Update(metadata.GetName(), obj)
if err == nil {
return nil
}
obj, err = o.objectClient.GetNamespaced(metadata.GetNamespace(), metadata.GetName(), metav1.GetOptions{})
if err != nil {
return err
}
}
return fmt.Errorf("failed to remove finalizer on %s", name)
}
func (o *objectLifecycleAdapter) createKey() string {
return created + "." + o.name
}
func (o *objectLifecycleAdapter) constructFinalizerKey() string {
if o.clusterScoped {
return ScopedFinalizerKey + o.name
}
return finalizerKey + o.name
}
func (o *objectLifecycleAdapter) create(metadata metav1.Object, obj runtime.Object) (bool, error) {
if o.isInitialized(metadata) {
return true, nil
}
copyObj := obj.DeepCopyObject()
copyObj, err := o.addFinalizer(copyObj)
if err != nil {
return false, err
}
if newObj, err := o.lifecycle.Create(copyObj); err != nil {
o.update(metadata.GetName(), obj, newObj)
return false, err
} else if newObj != nil {
copyObj = newObj
}
return false, o.setInitialized(copyObj)
}
func (o *objectLifecycleAdapter) isInitialized(metadata metav1.Object) bool {
initialized := o.createKey()
return metadata.GetAnnotations()[initialized] == "true"
}
func (o *objectLifecycleAdapter) setInitialized(obj runtime.Object) error {
metadata, err := meta.Accessor(obj)
if err != nil {
return err
}
initialized := o.createKey()
if metadata.GetAnnotations() == nil {
metadata.SetAnnotations(map[string]string{})
}
metadata.GetAnnotations()[initialized] = "true"
_, err = o.objectClient.Update(metadata.GetName(), obj)
return err
}
func (o *objectLifecycleAdapter) addFinalizer(obj runtime.Object) (runtime.Object, error) {
metadata, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
if slice.ContainsString(metadata.GetFinalizers(), o.constructFinalizerKey()) {
return obj, nil
}
metadata.SetFinalizers(append(metadata.GetFinalizers(), o.constructFinalizerKey()))
return o.objectClient.Update(metadata.GetName(), obj)
}

View File

@@ -1,58 +0,0 @@
package metrics
import (
"os"
"github.com/prometheus/client_golang/prometheus"
)
const MetricsGenericControllerEnv = "NORMAN_GENERIC_CONTROLLER_METRICS"
var (
genericControllerMetrics = false
TotalHandlerExecution = prometheus.NewCounterVec(
prometheus.CounterOpts{
Subsystem: "norman_generic_controller",
Name: "total_handler_execution",
Help: "Total Count of executing handler",
},
[]string{"name", "handlerName"},
)
TotalHandlerFailure = prometheus.NewCounterVec(
prometheus.CounterOpts{
Subsystem: "norman_generic_controller",
Name: "total_handler_failure",
Help: "Total Count of handler failure",
},
[]string{"name", "handlerName", "key"},
)
)
func init() {
if os.Getenv(MetricsGenericControllerEnv) == "true" {
genericControllerMetrics = true
}
}
func IncTotalHandlerExecution(controllerName, handlerName string) {
if genericControllerMetrics {
TotalHandlerExecution.With(
prometheus.Labels{
"name": controllerName,
"handlerName": handlerName},
).Inc()
}
}
func IncTotalHandlerFailure(controllerName, handlerName, key string) {
if genericControllerMetrics {
TotalHandlerFailure.With(
prometheus.Labels{
"name": controllerName,
"handlerName": handlerName,
"key": key,
},
).Inc()
}
}

View File

@@ -1,11 +0,0 @@
package register
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/rancher/norman/metrics"
)
func init() {
prometheus.MustRegister(metrics.TotalHandlerExecution)
prometheus.MustRegister(metrics.TotalHandlerFailure)
}

View File

@@ -1,27 +0,0 @@
package name
import "strings"
func GuessPluralName(name string) string {
if name == "" {
return name
}
if strings.EqualFold(name, "Endpoints") {
return name
}
if suffix(name, "s") || suffix(name, "ch") || suffix(name, "x") {
return name + "es"
}
if suffix(name, "y") && len(name) > 2 && !strings.ContainsAny(name[len(name)-2:len(name)-1], "[aeiou]") {
return name[0:len(name)-1] + "ies"
}
return name + "s"
}
func suffix(str, end string) bool {
return strings.HasSuffix(str, end)
}

View File

@@ -1,58 +0,0 @@
package dynamic
import (
ejson "encoding/json"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var (
NegotiatedSerializer = negotiatedSerializer{}
)
type negotiatedSerializer struct{}
func (s negotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
return []runtime.SerializerInfo{
{
MediaType: "application/json",
EncodesAsText: true,
Serializer: dynamicCodec{
Encoder: unstructured.UnstructuredJSONScheme,
},
},
}
}
func (s negotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return encoder
}
func (s negotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return decoder
}
type dynamicCodec struct {
runtime.Encoder
}
func (dynamicCodec) Decode(data []byte, gvk *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
obj, gvk, err := unstructured.UnstructuredJSONScheme.Decode(data, gvk, obj)
if err != nil {
return nil, nil, err
}
if _, ok := obj.(*metav1.Status); !ok && strings.ToLower(gvk.Kind) == "status" {
obj = &metav1.Status{}
err := ejson.Unmarshal(data, obj)
if err != nil {
return nil, nil, err
}
}
return obj, gvk, nil
}

View File

@@ -1,310 +0,0 @@
package objectclient
import (
"encoding/json"
"strings"
"github.com/pkg/errors"
"github.com/rancher/norman/restwatch"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
json2 "k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/rest"
restclientwatch "k8s.io/client-go/rest/watch"
)
type ObjectFactory interface {
Object() runtime.Object
List() runtime.Object
}
type UnstructuredObjectFactory struct {
}
func (u *UnstructuredObjectFactory) Object() runtime.Object {
return &unstructured.Unstructured{}
}
func (u *UnstructuredObjectFactory) List() runtime.Object {
return &unstructured.UnstructuredList{}
}
type GenericClient interface {
UnstructuredClient() GenericClient
GroupVersionKind() schema.GroupVersionKind
Create(o runtime.Object) (runtime.Object, error)
GetNamespaced(namespace, name string, opts metav1.GetOptions) (runtime.Object, error)
Get(name string, opts metav1.GetOptions) (runtime.Object, error)
Update(name string, o runtime.Object) (runtime.Object, error)
DeleteNamespaced(namespace, name string, opts *metav1.DeleteOptions) error
Delete(name string, opts *metav1.DeleteOptions) error
List(opts metav1.ListOptions) (runtime.Object, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
DeleteCollection(deleteOptions *metav1.DeleteOptions, listOptions metav1.ListOptions) error
Patch(name string, o runtime.Object, data []byte, subresources ...string) (runtime.Object, error)
ObjectFactory() ObjectFactory
}
type ObjectClient struct {
restClient rest.Interface
resource *metav1.APIResource
gvk schema.GroupVersionKind
ns string
Factory ObjectFactory
}
func NewObjectClient(namespace string, restClient rest.Interface, apiResource *metav1.APIResource, gvk schema.GroupVersionKind, factory ObjectFactory) *ObjectClient {
return &ObjectClient{
restClient: restClient,
resource: apiResource,
gvk: gvk,
ns: namespace,
Factory: factory,
}
}
func (p *ObjectClient) UnstructuredClient() GenericClient {
return &ObjectClient{
restClient: p.restClient,
resource: p.resource,
gvk: p.gvk,
ns: p.ns,
Factory: &UnstructuredObjectFactory{},
}
}
func (p *ObjectClient) GroupVersionKind() schema.GroupVersionKind {
return p.gvk
}
func (p *ObjectClient) getAPIPrefix() string {
if p.gvk.Group == "" {
return "api"
}
return "apis"
}
func (p *ObjectClient) Create(o runtime.Object) (runtime.Object, error) {
ns := p.ns
obj, ok := o.(metav1.Object)
if ok && obj.GetNamespace() != "" {
ns = obj.GetNamespace()
}
if ok {
labels := obj.GetLabels()
if labels == nil {
labels = make(map[string]string)
}
labels["cattle.io/creator"] = "norman"
obj.SetLabels(labels)
}
if t, err := meta.TypeAccessor(o); err == nil {
if t.GetKind() == "" {
t.SetKind(p.gvk.Kind)
}
if t.GetAPIVersion() == "" {
apiVersion, _ := p.gvk.ToAPIVersionAndKind()
t.SetAPIVersion(apiVersion)
}
}
result := p.Factory.Object()
logrus.Debugf("REST CREATE %s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, ns, p.resource.Name)
err := p.restClient.Post().
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
NamespaceIfScoped(ns, p.resource.Namespaced).
Resource(p.resource.Name).
Body(o).
Do().
Into(result)
return result, err
}
func (p *ObjectClient) GetNamespaced(namespace, name string, opts metav1.GetOptions) (runtime.Object, error) {
result := p.Factory.Object()
req := p.restClient.Get().
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version)
if namespace != "" {
req = req.Namespace(namespace)
}
err := req.
Resource(p.resource.Name).
VersionedParams(&opts, metav1.ParameterCodec).
Name(name).
Do().
Into(result)
logrus.Debugf("REST GET %s/%s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, namespace, p.resource.Name, name)
return result, err
}
func (p *ObjectClient) Get(name string, opts metav1.GetOptions) (runtime.Object, error) {
result := p.Factory.Object()
err := p.restClient.Get().
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
NamespaceIfScoped(p.ns, p.resource.Namespaced).
Resource(p.resource.Name).
VersionedParams(&opts, metav1.ParameterCodec).
Name(name).
Do().
Into(result)
logrus.Debugf("REST GET %s/%s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, p.ns, p.resource.Name, name)
return result, err
}
func (p *ObjectClient) Update(name string, o runtime.Object) (runtime.Object, error) {
ns := p.ns
if obj, ok := o.(metav1.Object); ok && obj.GetNamespace() != "" {
ns = obj.GetNamespace()
}
result := p.Factory.Object()
if len(name) == 0 {
return result, errors.New("object missing name")
}
logrus.Debugf("REST UPDATE %s/%s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, ns, p.resource.Name, name)
err := p.restClient.Put().
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
NamespaceIfScoped(ns, p.resource.Namespaced).
Resource(p.resource.Name).
Name(name).
Body(o).
Do().
Into(result)
return result, err
}
func (p *ObjectClient) DeleteNamespaced(namespace, name string, opts *metav1.DeleteOptions) error {
req := p.restClient.Delete().
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version)
if namespace != "" {
req = req.Namespace(namespace)
}
logrus.Debugf("REST DELETE %s/%s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, namespace, p.resource.Name, name)
return req.Resource(p.resource.Name).
Name(name).
Body(opts).
Do().
Error()
}
func (p *ObjectClient) Delete(name string, opts *metav1.DeleteOptions) error {
logrus.Debugf("REST DELETE %s/%s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, p.ns, p.resource.Name, name)
return p.restClient.Delete().
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
NamespaceIfScoped(p.ns, p.resource.Namespaced).
Resource(p.resource.Name).
Name(name).
Body(opts).
Do().
Error()
}
func (p *ObjectClient) List(opts metav1.ListOptions) (runtime.Object, error) {
result := p.Factory.List()
logrus.Debugf("REST LIST %s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, p.ns, p.resource.Name)
return result, p.restClient.Get().
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
NamespaceIfScoped(p.ns, p.resource.Namespaced).
Resource(p.resource.Name).
VersionedParams(&opts, metav1.ParameterCodec).
Do().
Into(result)
}
func (p *ObjectClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
restClient := p.restClient
if watchClient, ok := restClient.(restwatch.WatchClient); ok {
restClient = watchClient.WatchClient()
}
r, err := restClient.Get().
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
Prefix("watch").
NamespaceIfScoped(p.ns, p.resource.Namespaced).
Resource(p.resource.Name).
VersionedParams(&opts, metav1.ParameterCodec).
Stream()
if err != nil {
return nil, err
}
embeddedDecoder := &structuredDecoder{
factory: p.Factory,
}
streamDecoder := streaming.NewDecoder(json2.Framer.NewFrameReader(r), embeddedDecoder)
decoder := restclientwatch.NewDecoder(streamDecoder, embeddedDecoder)
return watch.NewStreamWatcher(decoder), nil
}
type structuredDecoder struct {
factory ObjectFactory
}
func (d *structuredDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
if into == nil {
into = d.factory.Object()
}
err := json.Unmarshal(data, &into)
if err != nil {
status := &metav1.Status{}
if err := json.Unmarshal(data, status); err == nil && strings.ToLower(status.Kind) == "status" {
return status, defaults, nil
}
return nil, nil, err
}
if _, ok := into.(*metav1.Status); !ok && strings.ToLower(into.GetObjectKind().GroupVersionKind().Kind) == "status" {
into = &metav1.Status{}
err := json.Unmarshal(data, into)
if err != nil {
return nil, nil, err
}
}
return into, defaults, err
}
func (p *ObjectClient) DeleteCollection(deleteOptions *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
return p.restClient.Delete().
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
NamespaceIfScoped(p.ns, p.resource.Namespaced).
Resource(p.resource.Name).
VersionedParams(&listOptions, metav1.ParameterCodec).
Body(deleteOptions).
Do().
Error()
}
func (p *ObjectClient) Patch(name string, o runtime.Object, data []byte, subresources ...string) (runtime.Object, error) {
ns := p.ns
if obj, ok := o.(metav1.Object); ok && obj.GetNamespace() != "" {
ns = obj.GetNamespace()
}
result := p.Factory.Object()
if len(name) == 0 {
return result, errors.New("object missing name")
}
err := p.restClient.Patch(types.StrategicMergePatchType).
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
NamespaceIfScoped(ns, p.resource.Namespaced).
Resource(p.resource.Name).
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return result, err
}
func (p *ObjectClient) ObjectFactory() ObjectFactory {
return p.Factory
}

View File

@@ -1,18 +0,0 @@
package parse
import (
"net/http"
"strings"
)
func IsBrowser(req *http.Request, checkAccepts bool) bool {
accepts := strings.ToLower(req.Header.Get("Accept"))
userAgent := strings.ToLower(req.Header.Get("User-Agent"))
if accepts == "" || !checkAccepts {
accepts = "*/*"
}
// User agent has Mozilla and browser accepts */*
return strings.Contains(userAgent, "mozilla") && strings.Contains(accepts, "*/*")
}

View File

@@ -1,500 +0,0 @@
package builder
import (
"errors"
"fmt"
"strings"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/definition"
"k8s.io/apimachinery/pkg/util/validation"
)
var (
Create = Operation("create")
Update = Operation("update")
Action = Operation("action")
List = Operation("list")
ListForCreate = Operation("listcreate")
ErrComplexType = errors.New("complex type")
)
type Operation string
func (o Operation) IsList() bool {
return strings.HasPrefix(string(o), "list")
}
type Builder struct {
apiContext *types.APIContext
Version *types.APIVersion
Schemas *types.Schemas
RefValidator types.ReferenceValidator
edit bool
export bool
yaml bool
}
func NewBuilder(apiRequest *types.APIContext) *Builder {
return &Builder{
apiContext: apiRequest,
yaml: apiRequest.ResponseFormat == "yaml",
edit: apiRequest.Option("edit") == "true",
export: apiRequest.Option("export") == "true",
Version: apiRequest.Version,
Schemas: apiRequest.Schemas,
RefValidator: apiRequest.ReferenceValidator,
}
}
func (b *Builder) Construct(schema *types.Schema, input map[string]interface{}, op Operation) (map[string]interface{}, error) {
result, err := b.copyFields(schema, input, op)
if err != nil {
return nil, err
}
if (op == Create || op == Update) && schema.Validator != nil {
if err := schema.Validator(b.apiContext, schema, result); err != nil {
return nil, err
}
}
return result, nil
}
func (b *Builder) copyInputs(schema *types.Schema, input map[string]interface{}, op Operation, result map[string]interface{}) error {
for fieldName, value := range input {
field, ok := schema.ResourceFields[fieldName]
if !ok {
continue
}
if !fieldMatchesOp(field, op) {
continue
}
wasNull := value == nil && (field.Nullable || field.Default == nil)
value, err := b.convert(field.Type, value, op)
if err != nil {
return httperror.WrapFieldAPIError(err, httperror.InvalidFormat, fieldName, err.Error())
}
if value != nil || wasNull {
if !op.IsList() {
if slice, ok := value.([]interface{}); ok {
for _, sliceValue := range slice {
if sliceValue == nil {
return httperror.NewFieldAPIError(httperror.NotNullable, fieldName, "Individual array values can not be null")
}
if err := CheckFieldCriteria(fieldName, field, sliceValue); err != nil {
return err
}
}
} else {
if err := CheckFieldCriteria(fieldName, field, value); err != nil {
return err
}
}
}
result[fieldName] = value
if op.IsList() && field.Type == "date" && value != "" && !b.edit {
ts, err := convert.ToTimestamp(value)
if err == nil {
result[fieldName+"TS"] = ts
}
}
}
}
if op.IsList() && !b.edit && !b.export {
if !convert.IsAPIObjectEmpty(input["type"]) {
result["type"] = input["type"]
}
if !convert.IsAPIObjectEmpty(input["id"]) {
result["id"] = input["id"]
}
}
return nil
}
func (b *Builder) checkDefaultAndRequired(schema *types.Schema, input map[string]interface{}, op Operation, result map[string]interface{}) error {
for fieldName, field := range schema.ResourceFields {
val, hasKey := result[fieldName]
if op == Create && (!hasKey || val == "") && field.Default != nil {
result[fieldName] = field.Default
}
_, hasKey = result[fieldName]
if op == Create && fieldMatchesOp(field, Create) && field.Required {
if !hasKey {
return httperror.NewFieldAPIError(httperror.MissingRequired, fieldName, "")
}
if definition.IsArrayType(field.Type) {
slice, err := b.convertArray(field.Type, result[fieldName], op)
if err != nil {
return err
}
if len(slice) == 0 {
return httperror.NewFieldAPIError(httperror.MissingRequired, fieldName, "")
}
}
}
if op.IsList() && fieldMatchesOp(field, List) && definition.IsReferenceType(field.Type) && !hasKey {
result[fieldName] = nil
} else if op.IsList() && fieldMatchesOp(field, List) && !hasKey && field.Default != nil {
result[fieldName] = field.Default
}
}
if op.IsList() && b.edit {
b.populateMissingFieldsForEdit(schema, result)
}
if op.IsList() && b.export {
b.dropDefaultsAndReadOnly(schema, result)
}
return nil
}
func (b *Builder) dropDefaultsAndReadOnly(schema *types.Schema, result map[string]interface{}) {
for name, existingVal := range result {
field, ok := schema.ResourceFields[name]
if !ok {
delete(result, name)
}
if !field.Create {
delete(result, name)
continue
}
if field.Default == existingVal {
delete(result, name)
continue
}
val, err := b.convert(field.Type, nil, List)
if err == nil && val == existingVal {
delete(result, name)
continue
}
if convert.IsAPIObjectEmpty(existingVal) {
delete(result, name)
continue
}
}
}
func (b *Builder) populateMissingFieldsForEdit(schema *types.Schema, result map[string]interface{}) {
for name, field := range schema.ResourceFields {
if !field.Update {
if name != "name" {
delete(result, name)
}
continue
}
desc := field.Description
if len(desc) > 0 {
desc += " "
}
value, hasKey := result[name]
if hasKey {
if field.Default != nil && field.Default == value {
delete(result, name)
result["zzz#("+desc+")("+field.Type+")"+name] = value
}
continue
}
if field.Default != nil {
result["zzz#("+desc+")("+field.Type+")"+name] = field.Default
} else {
val, err := b.convert(field.Type, nil, List)
if err == nil {
result["zzz#("+desc+")("+field.Type+")"+name] = val
}
}
}
}
func (b *Builder) copyFields(schema *types.Schema, input map[string]interface{}, op Operation) (map[string]interface{}, error) {
result := map[string]interface{}{}
if err := b.copyInputs(schema, input, op, result); err != nil {
return nil, err
}
return result, b.checkDefaultAndRequired(schema, input, op, result)
}
func CheckFieldCriteria(fieldName string, field types.Field, value interface{}) error {
numVal, isNum := value.(int64)
strVal := ""
hasStrVal := false
if value == nil && field.Default != nil {
value = field.Default
}
if value != nil && value != "" {
hasStrVal = true
strVal = fmt.Sprint(value)
}
if (value == nil || value == "") && !field.Nullable {
if field.Default == nil {
return httperror.NewFieldAPIError(httperror.NotNullable, fieldName, "")
}
}
if isNum {
if field.Min != nil && numVal < *field.Min {
return httperror.NewFieldAPIError(httperror.MinLimitExceeded, fieldName, "")
}
if field.Max != nil && numVal > *field.Max {
return httperror.NewFieldAPIError(httperror.MaxLimitExceeded, fieldName, "")
}
}
if hasStrVal {
if field.MinLength != nil && int64(len(strVal)) < *field.MinLength {
return httperror.NewFieldAPIError(httperror.MinLengthExceeded, fieldName, "")
}
if field.MaxLength != nil && int64(len(strVal)) > *field.MaxLength {
return httperror.NewFieldAPIError(httperror.MaxLengthExceeded, fieldName, "")
}
}
if len(field.Options) > 0 {
if hasStrVal || !field.Nullable {
found := false
for _, option := range field.Options {
if strVal == option {
found = true
break
}
}
if !found {
return httperror.NewFieldAPIError(httperror.InvalidOption, fieldName, "")
}
}
}
if len(field.ValidChars) > 0 && hasStrVal {
for _, c := range strVal {
if !strings.ContainsRune(field.ValidChars, c) {
return httperror.NewFieldAPIError(httperror.InvalidCharacters, fieldName, "")
}
}
}
if len(field.InvalidChars) > 0 && hasStrVal {
if strings.ContainsAny(strVal, field.InvalidChars) {
return httperror.NewFieldAPIError(httperror.InvalidCharacters, fieldName, "")
}
}
return nil
}
func ConvertSimple(fieldType string, value interface{}, op Operation) (interface{}, error) {
if value == nil {
return value, nil
}
switch fieldType {
case "json":
return value, nil
case "date":
v := convert.ToString(value)
if v == "" {
return nil, nil
}
return v, nil
case "boolean":
return convert.ToBool(value), nil
case "enum":
return convert.ToString(value), nil
case "int":
return convert.ToNumber(value)
case "password":
return convert.ToString(value), nil
case "string":
if op.IsList() {
return convert.ToStringNoTrim(value), nil
}
return convert.ToString(value), nil
case "dnsLabel":
str := convert.ToString(value)
if str == "" {
return "", nil
}
if op == Create || op == Update {
if errs := validation.IsDNS1123Label(str); len(errs) != 0 {
return value, httperror.NewAPIError(httperror.InvalidFormat, fmt.Sprintf("invalid value %s: %s", value,
strings.Join(errs, ",")))
}
}
return str, nil
case "dnsLabelRestricted":
str := convert.ToString(value)
if str == "" {
return "", nil
}
if op == Create || op == Update {
if errs := validation.IsDNS1035Label(str); len(errs) != 0 {
return value, httperror.NewAPIError(httperror.InvalidFormat, fmt.Sprintf("invalid value %s: %s", value,
strings.Join(errs, ",")))
}
}
return str, nil
case "hostname":
str := convert.ToString(value)
if str == "" {
return "", nil
}
if op == Create || op == Update {
if errs := validation.IsDNS1123Subdomain(str); len(errs) != 0 {
return value, httperror.NewAPIError(httperror.InvalidFormat, fmt.Sprintf("invalid value %s: %s", value,
strings.Join(errs, ",")))
}
}
return str, nil
case "intOrString":
num, err := convert.ToNumber(value)
if err == nil {
return num, nil
}
return convert.ToString(value), nil
case "base64":
return convert.ToString(value), nil
case "reference":
return convert.ToString(value), nil
}
return nil, ErrComplexType
}
func (b *Builder) convert(fieldType string, value interface{}, op Operation) (interface{}, error) {
if value == nil {
return value, nil
}
switch {
case definition.IsMapType(fieldType):
return b.convertMap(fieldType, value, op)
case definition.IsArrayType(fieldType):
return b.convertArray(fieldType, value, op)
case definition.IsReferenceType(fieldType):
return b.convertReferenceType(fieldType, value)
}
newValue, err := ConvertSimple(fieldType, value, op)
if err == ErrComplexType {
return b.convertType(fieldType, value, op)
}
return newValue, err
}
func (b *Builder) convertType(fieldType string, value interface{}, op Operation) (interface{}, error) {
schema := b.Schemas.Schema(b.Version, fieldType)
if schema == nil {
return nil, httperror.NewAPIError(httperror.InvalidType, "Failed to find type "+fieldType)
}
mapValue, ok := value.(map[string]interface{})
if !ok {
return nil, httperror.NewAPIError(httperror.InvalidFormat, fmt.Sprintf("Value can not be converted to type %s: %v", fieldType, value))
}
return b.Construct(schema, mapValue, op)
}
func (b *Builder) convertReferenceType(fieldType string, value interface{}) (string, error) {
subType := definition.SubType(fieldType)
strVal := convert.ToString(value)
if b.RefValidator != nil && !b.RefValidator.Validate(subType, strVal) {
return "", httperror.NewAPIError(httperror.InvalidReference, fmt.Sprintf("Not found type: %s id: %s", subType, strVal))
}
return strVal, nil
}
func (b *Builder) convertArray(fieldType string, value interface{}, op Operation) ([]interface{}, error) {
if strSliceValue, ok := value.([]string); ok {
// Form data will be []string
var result []interface{}
for _, value := range strSliceValue {
result = append(result, value)
}
return result, nil
}
sliceValue, ok := value.([]interface{})
if !ok {
return nil, nil
}
var result []interface{}
subType := definition.SubType(fieldType)
for _, value := range sliceValue {
val, err := b.convert(subType, value, op)
if err != nil {
return nil, err
}
result = append(result, val)
}
return result, nil
}
func (b *Builder) convertMap(fieldType string, value interface{}, op Operation) (map[string]interface{}, error) {
mapValue, ok := value.(map[string]interface{})
if !ok {
return nil, nil
}
result := map[string]interface{}{}
subType := definition.SubType(fieldType)
for key, value := range mapValue {
val, err := b.convert(subType, value, op)
if err != nil {
return nil, httperror.WrapAPIError(err, httperror.InvalidFormat, err.Error())
}
result[key] = val
}
return result, nil
}
func fieldMatchesOp(field types.Field, op Operation) bool {
switch op {
case Create:
return field.Create
case Update:
return field.Update
case List:
if field.Type == "password" {
return false
}
return !field.WriteOnly
case ListForCreate:
if field.Type == "password" {
return false
}
return true
default:
return false
}
}

View File

@@ -1,112 +0,0 @@
package parse
import (
"net/http"
"strconv"
"strings"
"github.com/rancher/norman/types"
)
var (
defaultLimit = int64(1000)
maxLimit = int64(3000)
)
func QueryOptions(apiContext *types.APIContext, schema *types.Schema) types.QueryOptions {
req := apiContext.Request
if req.Method != http.MethodGet {
return types.QueryOptions{}
}
result := &types.QueryOptions{}
result.Sort = parseSort(schema, apiContext)
result.Pagination = parsePagination(apiContext)
result.Conditions = parseFilters(schema, apiContext)
return *result
}
func parseOrder(apiContext *types.APIContext) types.SortOrder {
order := apiContext.Query.Get("order")
if types.SortOrder(order) == types.DESC {
return types.DESC
}
return types.ASC
}
func parseSort(schema *types.Schema, apiContext *types.APIContext) types.Sort {
sortField := apiContext.Query.Get("sort")
if _, ok := schema.CollectionFilters[sortField]; !ok {
sortField = ""
}
return types.Sort{
Order: parseOrder(apiContext),
Name: sortField,
}
}
func parsePagination(apiContext *types.APIContext) *types.Pagination {
if apiContext.Pagination != nil {
return apiContext.Pagination
}
q := apiContext.Query
limit := q.Get("limit")
marker := q.Get("marker")
result := &types.Pagination{
Limit: &defaultLimit,
Marker: marker,
}
if limit != "" {
limitInt, err := strconv.ParseInt(limit, 10, 64)
if err != nil {
return result
}
if limitInt > maxLimit {
result.Limit = &maxLimit
} else if limitInt >= 0 {
result.Limit = &limitInt
}
}
return result
}
func parseNameAndOp(value string) (string, types.ModifierType) {
name := value
op := "eq"
idx := strings.LastIndex(value, "_")
if idx > 0 {
op = value[idx+1:]
name = value[0:idx]
}
return name, types.ModifierType(op)
}
func parseFilters(schema *types.Schema, apiContext *types.APIContext) []*types.QueryCondition {
var conditions []*types.QueryCondition
for key, values := range apiContext.Query {
name, op := parseNameAndOp(key)
filter, ok := schema.CollectionFilters[name]
if !ok {
continue
}
for _, mod := range filter.Modifiers {
if op != mod || !types.ValidMod(op) {
continue
}
conditions = append(conditions, types.NewConditionFromString(name, mod, values...))
}
}
return conditions
}

View File

@@ -1,310 +0,0 @@
package parse
import (
"net/http"
"net/url"
"regexp"
"strings"
"sort"
"github.com/rancher/norman/api/builtin"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"github.com/rancher/norman/urlbuilder"
)
const (
maxFormSize = 2 * 1 << 20
)
var (
multiSlashRegexp = regexp.MustCompile("//+")
allowedFormats = map[string]bool{
"html": true,
"json": true,
"yaml": true,
}
)
type ParsedURL struct {
Version *types.APIVersion
SchemasVersion *types.APIVersion
Type string
ID string
Link string
Method string
Action string
SubContext map[string]string
SubContextPrefix string
Query url.Values
}
type ResolverFunc func(typeName string, context *types.APIContext) error
type URLParser func(schema *types.Schemas, url *url.URL) (ParsedURL, error)
func DefaultURLParser(schemas *types.Schemas, url *url.URL) (ParsedURL, error) {
result := ParsedURL{}
path := url.EscapedPath()
path = multiSlashRegexp.ReplaceAllString(path, "/")
schemaVersion, version, prefix, parts, subContext := parseVersionAndSubContext(schemas, path)
if version == nil {
return result, nil
}
result.Version = version
result.SchemasVersion = schemaVersion
result.SubContext = subContext
result.SubContextPrefix = prefix
result.Action, result.Method = parseAction(url)
result.Query = url.Query()
result.Type = safeIndex(parts, 0)
result.ID = safeIndex(parts, 1)
result.Link = safeIndex(parts, 2)
return result, nil
}
func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, urlParser URLParser, resolverFunc ResolverFunc) (*types.APIContext, error) {
var err error
result := types.NewAPIContext(req, rw, schemas)
result.Method = parseMethod(req)
result.ResponseFormat = parseResponseFormat(req)
result.URLBuilder, _ = urlbuilder.New(req, types.APIVersion{}, schemas)
// The response format is guarenteed to be set even in the event of an error
parsedURL, err := urlParser(schemas, req.URL)
// wait to check error, want to set as much as possible
result.SubContext = parsedURL.SubContext
result.Type = parsedURL.Type
result.ID = parsedURL.ID
result.Link = parsedURL.Link
result.Action = parsedURL.Action
result.Query = parsedURL.Query
if parsedURL.Method != "" {
result.Method = parsedURL.Method
}
result.Version = parsedURL.Version
result.SchemasVersion = parsedURL.SchemasVersion
if err != nil {
return result, err
}
if result.Version == nil {
result.Method = http.MethodGet
result.URLBuilder, err = urlbuilder.New(req, types.APIVersion{}, result.Schemas)
result.Type = "apiRoot"
result.Schema = result.Schemas.Schema(&builtin.Version, "apiRoot")
return result, nil
}
result.URLBuilder, err = urlbuilder.New(req, *result.Version, result.Schemas)
if err != nil {
return result, err
}
if parsedURL.SubContextPrefix != "" {
result.URLBuilder.SetSubContext(parsedURL.SubContextPrefix)
}
if err := resolverFunc(result.Type, result); err != nil {
return result, err
}
if result.Schema == nil {
if result.Type != "" {
err = httperror.NewAPIError(httperror.NotFound, "failed to find schema "+result.Type)
}
result.Method = http.MethodGet
result.Type = "apiRoot"
result.Schema = result.Schemas.Schema(&builtin.Version, "apiRoot")
result.ID = result.Version.Path
return result, err
}
result.Type = result.Schema.ID
if err := ValidateMethod(result); err != nil {
return result, err
}
return result, nil
}
func versionsForPath(schemas *types.Schemas, path string) []types.APIVersion {
var matchedVersion []types.APIVersion
for _, version := range schemas.Versions() {
if strings.HasPrefix(path, version.Path) {
afterPath := path[len(version.Path):]
// if version.Path is /v3/cluster allow /v3/clusters but not /v3/clusterstuff
if len(afterPath) < 3 || strings.Contains(afterPath[:3], "/") {
matchedVersion = append(matchedVersion, version)
}
}
}
sort.Slice(matchedVersion, func(i, j int) bool {
return len(matchedVersion[i].Path) > len(matchedVersion[j].Path)
})
return matchedVersion
}
func parseVersionAndSubContext(schemas *types.Schemas, escapedPath string) (*types.APIVersion, *types.APIVersion, string, []string, map[string]string) {
versions := versionsForPath(schemas, escapedPath)
if len(versions) == 0 {
return nil, nil, "", nil, nil
}
version := &versions[0]
if strings.HasSuffix(escapedPath, "/") {
escapedPath = escapedPath[:len(escapedPath)-1]
}
versionParts := strings.Split(version.Path, "/")
pp := strings.Split(escapedPath, "/")
var pathParts []string
for _, p := range pp {
part, err := url.PathUnescape(p)
if err == nil {
pathParts = append(pathParts, part)
} else {
pathParts = append(pathParts, p)
}
}
paths := pathParts[len(versionParts):]
if !version.SubContext || len(versions) < 2 {
return nil, version, "", paths, nil
}
// Handle the special case of /v3/clusters/schema(s)
if len(paths) >= 1 && (paths[0] == "schema" || paths[0] == "schemas") {
return nil, version, "", paths, nil
}
if len(paths) < 2 {
// Handle case like /v3/clusters/foo where /v3 and /v3/clusters are API versions.
// In this situation you want the version to be /v3 and the path "clusters", "foo"
newVersion := versions[0]
if len(paths) > 0 {
newVersion.Path = newVersion.Path + "/" + paths[0]
}
return &newVersion, &versions[1], "", pathParts[len(versionParts)-1:], nil
}
// Length is always >= 3
attrs := map[string]string{
version.SubContextSchema: paths[0],
}
for i, version := range versions {
schema := schemas.Schema(&version, paths[1])
if schema != nil {
if i == 0 {
break
}
return nil, &version, "", paths[1:], attrs
}
}
return nil, version, "/" + paths[0], paths[1:], attrs
}
func DefaultResolver(typeName string, apiContext *types.APIContext) error {
if typeName == "" {
return nil
}
schema := apiContext.Schemas.Schema(apiContext.Version, typeName)
if schema == nil && (typeName == builtin.Schema.ID || typeName == builtin.Schema.PluralName) {
// Schemas are special, we include it as though part of the API request version
schema = apiContext.Schemas.Schema(&builtin.Version, typeName)
}
if schema == nil {
return nil
}
apiContext.Schema = schema
return nil
}
func safeIndex(slice []string, index int) string {
if index >= len(slice) {
return ""
}
return slice[index]
}
func parseResponseFormat(req *http.Request) string {
format := req.URL.Query().Get("_format")
if format != "" {
format = strings.TrimSpace(strings.ToLower(format))
}
/* Format specified */
if allowedFormats[format] {
return format
}
// User agent has Mozilla and browser accepts */*
if IsBrowser(req, true) {
return "html"
}
if isYaml(req) {
return "yaml"
}
return "json"
}
func isYaml(req *http.Request) bool {
return strings.Contains(req.Header.Get("Accept"), "application/yaml")
}
func parseMethod(req *http.Request) string {
method := req.URL.Query().Get("_method")
if method == "" {
method = req.Method
}
return method
}
func parseAction(url *url.URL) (string, string) {
action := url.Query().Get("action")
if action == "remove" {
return "", http.MethodDelete
}
return action, ""
}
func Body(req *http.Request) (map[string]interface{}, error) {
req.ParseMultipartForm(maxFormSize)
if req.MultipartForm != nil {
return valuesToBody(req.MultipartForm.Value), nil
}
if req.PostForm != nil && len(req.PostForm) > 0 {
return valuesToBody(map[string][]string(req.Form)), nil
}
return ReadBody(req)
}
func valuesToBody(input map[string][]string) map[string]interface{} {
result := map[string]interface{}{}
for k, v := range input {
result[k] = v
}
return result
}

View File

@@ -1,45 +0,0 @@
package parse
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/rancher/norman/httperror"
"k8s.io/apimachinery/pkg/util/yaml"
)
const reqMaxSize = (2 * 1 << 20) + 1
var bodyMethods = map[string]bool{
http.MethodPut: true,
http.MethodPost: true,
}
type Decode func(interface{}) error
func ReadBody(req *http.Request) (map[string]interface{}, error) {
if !bodyMethods[req.Method] {
return nil, nil
}
decode := getDecoder(req, io.LimitReader(req.Body, maxFormSize))
data := map[string]interface{}{}
if err := decode(&data); err != nil {
return nil, httperror.NewAPIError(httperror.InvalidBodyContent,
fmt.Sprintf("Failed to parse body: %v", err))
}
return data, nil
}
func getDecoder(req *http.Request, reader io.Reader) Decode {
if req.Header.Get("Content-type") == "application/yaml" {
return yaml.NewYAMLToJSONDecoder(reader).Decode
}
decoder := json.NewDecoder(reader)
decoder.UseNumber()
return decoder.Decode
}

View File

@@ -1,50 +0,0 @@
package parse
import (
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
)
type DefaultSubContextAttributeProvider struct {
}
func (d *DefaultSubContextAttributeProvider) Query(apiContext *types.APIContext, schema *types.Schema) []*types.QueryCondition {
var result []*types.QueryCondition
for name, value := range d.create(apiContext, schema) {
result = append(result, types.NewConditionFromString(name, types.ModifierEQ, value))
}
return result
}
func (d *DefaultSubContextAttributeProvider) Create(apiContext *types.APIContext, schema *types.Schema) map[string]interface{} {
result := map[string]interface{}{}
for key, value := range d.create(apiContext, schema) {
result[key] = value
}
return result
}
func (d *DefaultSubContextAttributeProvider) create(apiContext *types.APIContext, schema *types.Schema) map[string]string {
result := map[string]string{}
for subContextSchemaID, value := range apiContext.SubContext {
subContextSchema := apiContext.Schemas.Schema(nil, subContextSchemaID)
if subContextSchema == nil {
continue
}
ref := convert.ToReference(subContextSchema.ID)
fullRef := convert.ToFullReference(subContextSchema.Version.Path, subContextSchema.ID)
for name, field := range schema.ResourceFields {
if field.Type == ref || field.Type == fullRef {
result[name] = value
break
}
}
}
return result
}

View File

@@ -1,45 +0,0 @@
package parse
import (
"fmt"
"net/http"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
)
var (
supportedMethods = map[string]bool{
http.MethodPost: true,
http.MethodGet: true,
http.MethodPut: true,
http.MethodDelete: true,
}
)
func ValidateMethod(request *types.APIContext) error {
if request.Action != "" && request.Method == http.MethodPost {
return nil
}
if !supportedMethods[request.Method] {
return httperror.NewAPIError(httperror.MethodNotAllowed, fmt.Sprintf("Method %s not supported", request.Method))
}
if request.Type == "" || request.Schema == nil || request.Link != "" {
return nil
}
allowed := request.Schema.ResourceMethods
if request.ID == "" {
allowed = request.Schema.CollectionMethods
}
for _, method := range allowed {
if method == request.Method {
return nil
}
}
return httperror.NewAPIError(httperror.MethodNotAllowed, fmt.Sprintf("Method %s not supported", request.Method))
}

View File

@@ -1,71 +0,0 @@
package changeset
import (
"strings"
"github.com/rancher/norman/controller"
"k8s.io/apimachinery/pkg/runtime"
)
type Key struct {
Namespace string
Name string
}
type ControllerProvider interface {
Generic() controller.GenericController
}
type Enqueuer func(namespace, name string)
type Resolver func(namespace, name string, obj runtime.Object) ([]Key, error)
func Watch(name string, resolve Resolver, enq Enqueuer, controllers ...ControllerProvider) {
for _, c := range controllers {
watch(name, enq, resolve, c.Generic())
}
}
func watch(name string, enq Enqueuer, resolve Resolver, genericController controller.GenericController) {
genericController.AddHandler(name, func(key string) error {
obj, exists, err := genericController.Informer().GetStore().GetByKey(key)
if err != nil {
return err
}
if !exists {
obj = nil
}
var (
ns string
name string
)
parts := strings.SplitN(key, "/", 2)
if len(parts) == 2 {
ns = parts[0]
name = parts[1]
} else {
name = parts[0]
}
ro, ok := obj.(runtime.Object)
if !ok {
ro = nil
}
keys, err := resolve(ns, name, ro)
if err != nil {
return err
}
for _, key := range keys {
if key.Name != "" {
enq(key.Namespace, key.Name)
}
}
return nil
})
}

View File

@@ -1,44 +0,0 @@
package dump
import (
"bytes"
"io"
"os"
"os/signal"
"runtime"
"github.com/maruel/panicparse/stack"
)
func GoroutineDumpOn(signals ...os.Signal) {
c := make(chan os.Signal, 1)
signal.Notify(c, signals...)
go func() {
for range c {
var (
buf []byte
stackSize int
)
bufferLen := 16384
for stackSize == len(buf) {
buf = make([]byte, bufferLen)
stackSize = runtime.Stack(buf, true)
bufferLen *= 2
}
buf = buf[:stackSize]
src := bytes.NewBuffer(buf)
if goroutines, err := stack.ParseDump(src, os.Stderr); err == nil {
buckets := stack.SortBuckets(stack.Bucketize(goroutines, stack.AnyValue))
srcLen, pkgLen := stack.CalcLengths(buckets, true)
p := &stack.Palette{}
for _, bucket := range buckets {
_, _ = io.WriteString(os.Stderr, p.BucketHeader(&bucket, true, len(buckets) > 1))
_, _ = io.WriteString(os.Stderr, p.StackLines(&bucket.Signature, srcLen, pkgLen, true))
}
io.Copy(os.Stderr, bytes.NewBuffer(buf))
} else {
io.Copy(os.Stderr, src)
}
}
}()
}

View File

@@ -1,24 +0,0 @@
package kv
import "strings"
func Split(s, sep string) (string, string) {
parts := strings.SplitN(s, sep, 2)
return strings.TrimSpace(parts[0]), strings.TrimSpace(safeIndex(parts, 1))
}
func SplitMap(s, sep string) map[string]string {
result := map[string]string{}
for _, part := range strings.Split(s, sep) {
k, v := Split(part, "=")
result[k] = v
}
return result
}
func safeIndex(parts []string, idx int) string {
if len(parts) <= idx {
return ""
}
return parts[idx]
}

View File

@@ -1,174 +0,0 @@
package subscribe
import (
"bytes"
"context"
"errors"
"time"
"github.com/gorilla/websocket"
"github.com/rancher/norman/api/writer"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/parse"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/slice"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
)
var upgrader = websocket.Upgrader{}
type Subscribe struct {
ResourceTypes []string
APIVersions []string
ProjectID string `norman:"type=reference[/v3/schemas/project]"`
}
func Handler(apiContext *types.APIContext, _ types.RequestHandler) error {
err := handler(apiContext)
if err != nil {
logrus.Errorf("Error during subscribe %v", err)
}
return err
}
func getMatchingSchemas(apiContext *types.APIContext) []*types.Schema {
resourceTypes := apiContext.Request.URL.Query()["resourceTypes"]
var schemas []*types.Schema
for _, schema := range apiContext.Schemas.SchemasForVersion(*apiContext.Version) {
if !matches(resourceTypes, schema.ID) {
continue
}
if schema.Store != nil {
schemas = append(schemas, schema)
}
}
return schemas
}
func handler(apiContext *types.APIContext) error {
schemas := getMatchingSchemas(apiContext)
if len(schemas) == 0 {
return httperror.NewAPIError(httperror.NotFound, "no resources types matched")
}
c, err := upgrader.Upgrade(apiContext.Response, apiContext.Request, nil)
if err != nil {
return err
}
defer c.Close()
cancelCtx, cancel := context.WithCancel(apiContext.Request.Context())
readerGroup, ctx := errgroup.WithContext(cancelCtx)
apiContext.Request = apiContext.Request.WithContext(ctx)
go func() {
for {
if _, _, err := c.NextReader(); err != nil {
cancel()
c.Close()
break
}
}
}()
events := make(chan map[string]interface{})
for _, schema := range schemas {
streamStore(ctx, readerGroup, apiContext, schema, events)
}
go func() {
readerGroup.Wait()
close(events)
}()
jsonWriter := writer.EncodingResponseWriter{
ContentType: "application/json",
Encoder: types.JSONEncoder,
}
t := time.NewTicker(5 * time.Second)
defer t.Stop()
done := false
for !done {
select {
case item, ok := <-events:
if !ok {
done = true
break
}
header := `{"name":"resource.change","data":`
if item[".removed"] == true {
header = `{"name":"resource.remove","data":`
}
schema := apiContext.Schemas.Schema(apiContext.Version, convert.ToString(item["type"]))
if schema != nil {
buffer := &bytes.Buffer{}
if err := jsonWriter.VersionBody(apiContext, &schema.Version, buffer, item); err != nil {
return err
}
if err := writeData(c, header, buffer.Bytes()); err != nil {
return err
}
}
case <-t.C:
if err := writeData(c, `{"name":"ping","data":`, []byte("{}")); err != nil {
return err
}
}
}
// no point in ever returning null because the connection is hijacked and we can't write it
return nil
}
func writeData(c *websocket.Conn, header string, buf []byte) error {
messageWriter, err := c.NextWriter(websocket.TextMessage)
if err != nil {
return err
}
if _, err := messageWriter.Write([]byte(header)); err != nil {
return err
}
if _, err := messageWriter.Write(buf); err != nil {
return err
}
if _, err := messageWriter.Write([]byte(`}`)); err != nil {
return err
}
return messageWriter.Close()
}
func streamStore(ctx context.Context, eg *errgroup.Group, apiContext *types.APIContext, schema *types.Schema, result chan map[string]interface{}) {
eg.Go(func() error {
opts := parse.QueryOptions(apiContext, schema)
events, err := schema.Store.Watch(apiContext, schema, &opts)
if err != nil || events == nil {
if err != nil {
logrus.Errorf("failed on subscribe %s: %v", schema.ID, err)
}
return err
}
logrus.Debugf("watching %s", schema.ID)
for e := range events {
result <- e
}
return errors.New("disconnect")
})
}
func matches(items []string, item string) bool {
if len(items) == 0 {
return true
}
return slice.ContainsString(items, item)
}

View File

@@ -1,16 +0,0 @@
package subscribe
import (
"net/http"
"github.com/rancher/norman/types"
)
func Register(version *types.APIVersion, schemas *types.Schemas) {
schemas.MustImportAndCustomize(version, Subscribe{}, func(schema *types.Schema) {
schema.CollectionMethods = []string{http.MethodGet}
schema.ResourceMethods = []string{}
schema.ListHandler = Handler
schema.PluralName = "subscribe"
})
}

View File

@@ -1,43 +0,0 @@
package restwatch
import (
"time"
"k8s.io/client-go/rest"
)
type WatchClient interface {
WatchClient() rest.Interface
}
func UnversionedRESTClientFor(config *rest.Config) (rest.Interface, error) {
client, err := rest.UnversionedRESTClientFor(config)
if err != nil {
return nil, err
}
if config.Timeout == 0 {
return client, err
}
newConfig := *config
newConfig.Timeout = time.Hour
watchClient, err := rest.UnversionedRESTClientFor(&newConfig)
if err != nil {
return nil, err
}
return &clientWithWatch{
RESTClient: client,
watchClient: watchClient,
}, nil
}
type clientWithWatch struct {
*rest.RESTClient
watchClient *rest.RESTClient
}
func (c *clientWithWatch) WatchClient() rest.Interface {
return c.watchClient
}

View File

@@ -1,28 +0,0 @@
package signal
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/sirupsen/logrus"
)
func SigTermCancelContext(ctx context.Context) context.Context {
term := make(chan os.Signal)
signal.Notify(term, os.Interrupt, syscall.SIGTERM)
ctx, cancel := context.WithCancel(ctx)
go func() {
select {
case <-term:
logrus.Infof("Received SIGTERM, cancelling")
cancel()
case <-ctx.Done():
}
}()
return ctx
}

View File

@@ -1,224 +0,0 @@
package crd
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/rancher/norman/store/proxy"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/sirupsen/logrus"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/rest"
)
type Factory struct {
wg sync.WaitGroup
ClientGetter proxy.ClientGetter
}
func NewFactoryFromClientGetter(clientGetter proxy.ClientGetter) *Factory {
return &Factory{
ClientGetter: clientGetter,
}
}
func NewFactoryFromClient(config rest.Config) (*Factory, error) {
getter, err := proxy.NewClientGetterFromConfig(config)
if err != nil {
return nil, err
}
return &Factory{
ClientGetter: getter,
}, nil
}
func (f *Factory) BatchWait() {
f.wg.Wait()
}
func (f *Factory) BatchCreateCRDs(ctx context.Context, storageContext types.StorageContext, schemas *types.Schemas, version *types.APIVersion, schemaIDs ...string) {
f.wg.Add(1)
go func() {
defer f.wg.Done()
var schemasToCreate []*types.Schema
for _, schemaID := range schemaIDs {
s := schemas.Schema(version, schemaID)
if s == nil {
panic("can not find schema " + schemaID)
}
schemasToCreate = append(schemasToCreate, s)
}
err := f.AssignStores(ctx, storageContext, schemasToCreate...)
if err != nil {
panic("creating CRD store " + err.Error())
}
}()
}
func (f *Factory) AssignStores(ctx context.Context, storageContext types.StorageContext, schemas ...*types.Schema) error {
schemaStatus, err := f.CreateCRDs(ctx, storageContext, schemas...)
if err != nil {
return err
}
for _, schema := range schemas {
crd, ok := schemaStatus[schema]
if !ok {
return fmt.Errorf("failed to create create/find CRD for %s", schema.ID)
}
schema.Store = proxy.NewProxyStore(ctx, f.ClientGetter,
storageContext,
[]string{"apis"},
crd.Spec.Group,
crd.Spec.Version,
crd.Status.AcceptedNames.Kind,
crd.Status.AcceptedNames.Plural)
}
return nil
}
func (f *Factory) CreateCRDs(ctx context.Context, storageContext types.StorageContext, schemas ...*types.Schema) (map[*types.Schema]*apiext.CustomResourceDefinition, error) {
schemaStatus := map[*types.Schema]*apiext.CustomResourceDefinition{}
apiClient, err := f.ClientGetter.APIExtClient(nil, storageContext)
if err != nil {
return nil, err
}
ready, err := f.getReadyCRDs(apiClient)
if err != nil {
return nil, err
}
for _, schema := range schemas {
crd, err := f.createCRD(apiClient, schema, ready)
if err != nil {
return nil, err
}
schemaStatus[schema] = crd
}
ready, err = f.getReadyCRDs(apiClient)
if err != nil {
return nil, err
}
for schema, crd := range schemaStatus {
if readyCrd, ok := ready[crd.Name]; ok {
schemaStatus[schema] = readyCrd
} else {
if err := f.waitCRD(ctx, apiClient, crd.Name, schema, schemaStatus); err != nil {
return nil, err
}
}
}
return schemaStatus, nil
}
func (f *Factory) waitCRD(ctx context.Context, apiClient clientset.Interface, crdName string, schema *types.Schema, schemaStatus map[*types.Schema]*apiext.CustomResourceDefinition) error {
logrus.Infof("Waiting for CRD %s to become available", crdName)
defer logrus.Infof("Done waiting for CRD %s to become available", crdName)
first := true
return wait.Poll(500*time.Millisecond, 60*time.Second, func() (bool, error) {
if !first {
logrus.Infof("Waiting for CRD %s to become available", crdName)
}
first = false
crd, err := apiClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(crdName, metav1.GetOptions{})
if err != nil {
return false, err
}
for _, cond := range crd.Status.Conditions {
switch cond.Type {
case apiext.Established:
if cond.Status == apiext.ConditionTrue {
schemaStatus[schema] = crd
return true, err
}
case apiext.NamesAccepted:
if cond.Status == apiext.ConditionFalse {
logrus.Infof("Name conflict on %s: %v\n", crdName, cond.Reason)
}
}
}
return false, ctx.Err()
})
}
func (f *Factory) createCRD(apiClient clientset.Interface, schema *types.Schema, ready map[string]*apiext.CustomResourceDefinition) (*apiext.CustomResourceDefinition, error) {
plural := strings.ToLower(schema.PluralName)
name := strings.ToLower(plural + "." + schema.Version.Group)
crd, ok := ready[name]
if ok {
return crd, nil
}
crd = &apiext.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: apiext.CustomResourceDefinitionSpec{
Group: schema.Version.Group,
Version: schema.Version.Version,
Names: apiext.CustomResourceDefinitionNames{
Plural: plural,
Kind: convert.Capitalize(schema.ID),
},
},
}
if schema.Scope == types.NamespaceScope {
crd.Spec.Scope = apiext.NamespaceScoped
} else {
crd.Spec.Scope = apiext.ClusterScoped
}
logrus.Infof("Creating CRD %s", name)
crd, err := apiClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)
if errors.IsAlreadyExists(err) {
return crd, nil
}
return crd, err
}
func (f *Factory) getReadyCRDs(apiClient clientset.Interface) (map[string]*apiext.CustomResourceDefinition, error) {
list, err := apiClient.ApiextensionsV1beta1().CustomResourceDefinitions().List(metav1.ListOptions{})
if err != nil {
return nil, err
}
result := map[string]*apiext.CustomResourceDefinition{}
for i, crd := range list.Items {
for _, cond := range crd.Status.Conditions {
switch cond.Type {
case apiext.Established:
if cond.Status == apiext.ConditionTrue {
result[crd.Name] = &list.Items[i]
}
}
}
}
return result, nil
}

View File

@@ -1,36 +0,0 @@
package empty
import (
"github.com/rancher/norman/types"
)
type Store struct {
}
func (e *Store) Context() types.StorageContext {
return types.DefaultStorageContext
}
func (e *Store) Delete(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
return nil, nil
}
func (e *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
return nil, nil
}
func (e *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
return nil, nil
}
func (e *Store) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
return nil, nil
}
func (e *Store) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
return nil, nil
}
func (e *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
return nil, nil
}

View File

@@ -1,52 +0,0 @@
package proxy
import (
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"k8s.io/apimachinery/pkg/api/errors"
)
type errorStore struct {
types.Store
}
func (e *errorStore) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
data, err := e.Store.ByID(apiContext, schema, id)
return data, translateError(err)
}
func (e *errorStore) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
data, err := e.Store.List(apiContext, schema, opt)
return data, translateError(err)
}
func (e *errorStore) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
data, err := e.Store.Create(apiContext, schema, data)
return data, translateError(err)
}
func (e *errorStore) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
data, err := e.Store.Update(apiContext, schema, data, id)
return data, translateError(err)
}
func (e *errorStore) Delete(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
data, err := e.Store.Delete(apiContext, schema, id)
return data, translateError(err)
}
func (e *errorStore) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
data, err := e.Store.Watch(apiContext, schema, opt)
return data, translateError(err)
}
func translateError(err error) error {
if apiError, ok := err.(errors.APIStatus); ok {
status := apiError.Status()
return httperror.NewAPIErrorLong(int(status.Code), string(status.Reason), status.Message)
}
return err
}

View File

@@ -1,475 +0,0 @@
package proxy
import (
"context"
ejson "encoding/json"
"net/http"
"strings"
"sync"
"time"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/objectclient/dynamic"
"github.com/rancher/norman/pkg/broadcast"
"github.com/rancher/norman/restwatch"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/convert/merge"
"github.com/rancher/norman/types/values"
"github.com/sirupsen/logrus"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/rest"
restclientwatch "k8s.io/client-go/rest/watch"
)
var (
userAuthHeader = "Impersonate-User"
authHeaders = []string{
userAuthHeader,
"Impersonate-Group",
}
)
type ClientGetter interface {
UnversionedClient(apiContext *types.APIContext, context types.StorageContext) (rest.Interface, error)
APIExtClient(apiContext *types.APIContext, context types.StorageContext) (clientset.Interface, error)
}
type simpleClientGetter struct {
restConfig rest.Config
client rest.Interface
apiExtClient clientset.Interface
}
func NewClientGetterFromConfig(config rest.Config) (ClientGetter, error) {
dynamicConfig := config
if dynamicConfig.NegotiatedSerializer == nil {
dynamicConfig.NegotiatedSerializer = dynamic.NegotiatedSerializer
}
unversionedClient, err := rest.UnversionedRESTClientFor(&dynamicConfig)
if err != nil {
return nil, err
}
apiExtClient, err := clientset.NewForConfig(&dynamicConfig)
if err != nil {
return nil, err
}
return &simpleClientGetter{
restConfig: config,
client: unversionedClient,
apiExtClient: apiExtClient,
}, nil
}
func (s *simpleClientGetter) Config(apiContext *types.APIContext, context types.StorageContext) (rest.Config, error) {
return s.restConfig, nil
}
func (s *simpleClientGetter) UnversionedClient(apiContext *types.APIContext, context types.StorageContext) (rest.Interface, error) {
return s.client, nil
}
func (s *simpleClientGetter) APIExtClient(apiContext *types.APIContext, context types.StorageContext) (clientset.Interface, error) {
return s.apiExtClient, nil
}
type Store struct {
sync.Mutex
clientGetter ClientGetter
storageContext types.StorageContext
prefix []string
group string
version string
kind string
resourcePlural string
authContext map[string]string
close context.Context
broadcasters map[rest.Interface]*broadcast.Broadcaster
}
func NewProxyStore(ctx context.Context, clientGetter ClientGetter, storageContext types.StorageContext,
prefix []string, group, version, kind, resourcePlural string) types.Store {
return &errorStore{
Store: &Store{
clientGetter: clientGetter,
storageContext: storageContext,
prefix: prefix,
group: group,
version: version,
kind: kind,
resourcePlural: resourcePlural,
authContext: map[string]string{
"apiGroup": group,
"resource": resourcePlural,
},
close: ctx,
broadcasters: map[rest.Interface]*broadcast.Broadcaster{},
},
}
}
func (s *Store) getUser(apiContext *types.APIContext) string {
return apiContext.Request.Header.Get(userAuthHeader)
}
func (s *Store) doAuthed(apiContext *types.APIContext, request *rest.Request) rest.Result {
start := time.Now()
defer func() {
logrus.Debug("GET: ", time.Now().Sub(start), s.resourcePlural)
}()
for _, header := range authHeaders {
request.SetHeader(header, apiContext.Request.Header[http.CanonicalHeaderKey(header)]...)
}
return request.Do()
}
func (s *Store) k8sClient(apiContext *types.APIContext) (rest.Interface, error) {
return s.clientGetter.UnversionedClient(apiContext, s.storageContext)
}
func (s *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
splitted := strings.Split(strings.TrimSpace(id), ":")
validID := false
namespaced := schema.Scope == types.NamespaceScope
if namespaced {
validID = len(splitted) == 2 && len(strings.TrimSpace(splitted[0])) > 0 && len(strings.TrimSpace(splitted[1])) > 0
} else {
validID = len(splitted) == 1 && len(strings.TrimSpace(splitted[0])) > 0
}
if !validID {
return nil, httperror.NewAPIError(httperror.NotFound, "failed to find resource by id")
}
_, result, err := s.byID(apiContext, schema, id)
return result, err
}
func (s *Store) byID(apiContext *types.APIContext, schema *types.Schema, id string) (string, map[string]interface{}, error) {
namespace, id := splitID(id)
k8sClient, err := s.k8sClient(apiContext)
if err != nil {
return "", nil, err
}
req := s.common(namespace, k8sClient.Get()).
Name(id)
return s.singleResult(apiContext, schema, req)
}
func (s *Store) Context() types.StorageContext {
return s.storageContext
}
func (s *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
namespace := getNamespace(apiContext, opt)
k8sClient, err := s.k8sClient(apiContext)
if err != nil {
return nil, err
}
req := s.common(namespace, k8sClient.Get())
resultList := &unstructured.UnstructuredList{}
start := time.Now()
err = req.Do().Into(resultList)
logrus.Debug("LIST: ", time.Now().Sub(start), s.resourcePlural)
if err != nil {
return nil, err
}
var result []map[string]interface{}
for _, obj := range resultList.Items {
result = append(result, s.fromInternal(apiContext, schema, obj.Object))
}
return apiContext.AccessControl.FilterList(apiContext, schema, result, s.authContext), nil
}
func (s *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
c, err := s.shareWatch(apiContext, schema, opt)
if err != nil {
return nil, err
}
return convert.Chan(c, func(data map[string]interface{}) map[string]interface{} {
return apiContext.AccessControl.Filter(apiContext, schema, data, s.authContext)
}), nil
}
func (s *Store) realWatch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
namespace := getNamespace(apiContext, opt)
k8sClient, err := s.k8sClient(apiContext)
if err != nil {
return nil, err
}
if watchClient, ok := k8sClient.(restwatch.WatchClient); ok {
k8sClient = watchClient.WatchClient()
}
timeout := int64(60 * 60)
req := s.common(namespace, k8sClient.Get())
req.VersionedParams(&metav1.ListOptions{
Watch: true,
TimeoutSeconds: &timeout,
ResourceVersion: "0",
}, metav1.ParameterCodec)
body, err := req.Stream()
if err != nil {
return nil, err
}
framer := json.Framer.NewFrameReader(body)
decoder := streaming.NewDecoder(framer, &unstructuredDecoder{})
watcher := watch.NewStreamWatcher(restclientwatch.NewDecoder(decoder, &unstructuredDecoder{}))
watchingContext, cancelWatchingContext := context.WithCancel(apiContext.Request.Context())
go func() {
<-watchingContext.Done()
logrus.Debugf("stopping watcher for %s", schema.ID)
watcher.Stop()
}()
result := make(chan map[string]interface{})
go func() {
for event := range watcher.ResultChan() {
data := event.Object.(*unstructured.Unstructured)
s.fromInternal(apiContext, schema, data.Object)
if event.Type == watch.Deleted && data.Object != nil {
data.Object[".removed"] = true
}
result <- data.Object
}
logrus.Debugf("closing watcher for %s", schema.ID)
close(result)
cancelWatchingContext()
}()
return result, nil
}
type unstructuredDecoder struct {
}
func (d *unstructuredDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
if into == nil {
into = &unstructured.Unstructured{}
}
return into, defaults, ejson.Unmarshal(data, &into)
}
func getNamespace(apiContext *types.APIContext, opt *types.QueryOptions) string {
if val, ok := apiContext.SubContext["namespaces"]; ok {
return convert.ToString(val)
}
for _, condition := range opt.Conditions {
mod := condition.ToCondition().Modifier
if condition.Field == "namespaceId" && condition.Value != "" && mod == types.ModifierEQ {
return condition.Value
}
if condition.Field == "namespace" && condition.Value != "" && mod == types.ModifierEQ {
return condition.Value
}
}
return ""
}
func (s *Store) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
if err := s.toInternal(schema.Mapper, data); err != nil {
return nil, err
}
namespace, _ := values.GetValueN(data, "metadata", "namespace").(string)
values.PutValue(data, s.getUser(apiContext), "metadata", "annotations", "field.cattle.io/creatorId")
values.PutValue(data, "norman", "metadata", "labels", "cattle.io/creator")
name, _ := values.GetValueN(data, "metadata", "name").(string)
if name == "" {
generated, _ := values.GetValueN(data, "metadata", "generateName").(string)
if generated == "" {
values.PutValue(data, types.GenerateName(schema.ID), "metadata", "name")
}
}
k8sClient, err := s.k8sClient(apiContext)
if err != nil {
return nil, err
}
req := s.common(namespace, k8sClient.Post()).
Body(&unstructured.Unstructured{
Object: data,
})
_, result, err := s.singleResult(apiContext, schema, req)
return result, err
}
func (s *Store) toInternal(mapper types.Mapper, data map[string]interface{}) error {
if mapper != nil {
if err := mapper.ToInternal(data); err != nil {
return err
}
}
if s.group == "" {
data["apiVersion"] = s.version
} else {
data["apiVersion"] = s.group + "/" + s.version
}
data["kind"] = s.kind
return nil
}
func (s *Store) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
var (
result map[string]interface{}
err error
)
k8sClient, err := s.k8sClient(apiContext)
if err != nil {
return nil, err
}
namespace, id := splitID(id)
if err := s.toInternal(schema.Mapper, data); err != nil {
return nil, err
}
for i := 0; i < 5; i++ {
req := s.common(namespace, k8sClient.Get()).
Name(id)
resourceVersion, existing, rawErr := s.singleResultRaw(apiContext, schema, req)
if rawErr != nil {
return nil, rawErr
}
existing = merge.APIUpdateMerge(schema.InternalSchema, apiContext.Schemas, existing, data, apiContext.Option("replace") == "true")
values.PutValue(existing, resourceVersion, "metadata", "resourceVersion")
values.PutValue(existing, namespace, "metadata", "namespace")
values.PutValue(existing, id, "metadata", "name")
req = s.common(namespace, k8sClient.Put()).
Body(&unstructured.Unstructured{
Object: existing,
}).
Name(id)
_, result, err = s.singleResult(apiContext, schema, req)
if errors.IsConflict(err) {
continue
}
return result, err
}
return result, err
}
func (s *Store) Delete(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
k8sClient, err := s.k8sClient(apiContext)
if err != nil {
return nil, err
}
namespace, name := splitID(id)
prop := metav1.DeletePropagationBackground
req := s.common(namespace, k8sClient.Delete()).
Body(&metav1.DeleteOptions{
PropagationPolicy: &prop,
}).
Name(name)
err = s.doAuthed(apiContext, req).Error()
if err != nil {
return nil, err
}
obj, err := s.ByID(apiContext, schema, id)
if err != nil {
return nil, nil
}
return obj, nil
}
func (s *Store) singleResult(apiContext *types.APIContext, schema *types.Schema, req *rest.Request) (string, map[string]interface{}, error) {
version, data, err := s.singleResultRaw(apiContext, schema, req)
if err != nil {
return "", nil, err
}
s.fromInternal(apiContext, schema, data)
return version, data, nil
}
func (s *Store) singleResultRaw(apiContext *types.APIContext, schema *types.Schema, req *rest.Request) (string, map[string]interface{}, error) {
result := &unstructured.Unstructured{}
err := s.doAuthed(apiContext, req).Into(result)
if err != nil {
return "", nil, err
}
return result.GetResourceVersion(), result.Object, nil
}
func splitID(id string) (string, string) {
namespace := ""
parts := strings.SplitN(id, ":", 2)
if len(parts) == 2 {
namespace = parts[0]
id = parts[1]
}
return namespace, id
}
func (s *Store) common(namespace string, req *rest.Request) *rest.Request {
prefix := append([]string{}, s.prefix...)
if s.group != "" {
prefix = append(prefix, s.group)
}
prefix = append(prefix, s.version)
req.Prefix(prefix...).
Resource(s.resourcePlural)
if namespace != "" {
req.Namespace(namespace)
}
return req
}
func (s *Store) fromInternal(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) map[string]interface{} {
if apiContext.Option("export") == "true" {
delete(data, "status")
}
if schema.Mapper != nil {
schema.Mapper.FromInternal(data)
}
return data
}

View File

@@ -1,28 +0,0 @@
package proxy
import (
"github.com/rancher/norman/pkg/broadcast"
"github.com/rancher/norman/types"
)
func (s *Store) shareWatch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
client, err := s.clientGetter.UnversionedClient(apiContext, s.Context())
if err != nil {
return nil, err
}
var b *broadcast.Broadcaster
s.Lock()
b, ok := s.broadcasters[client]
if !ok {
b = &broadcast.Broadcaster{}
s.broadcasters[client] = b
}
s.Unlock()
return b.Subscribe(apiContext.Request.Context(), func() (chan map[string]interface{}, error) {
newAPIContext := *apiContext
newAPIContext.Request = apiContext.Request.WithContext(s.close)
return s.realWatch(&newAPIContext, schema, &types.QueryOptions{})
})
}

View File

@@ -1,140 +0,0 @@
package schema
import (
"encoding/json"
"net/http"
"strings"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/store/empty"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/definition"
"github.com/rancher/norman/types/slice"
)
type Store struct {
empty.Store
}
func NewSchemaStore() types.Store {
return &Store{}
}
func (s *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
for _, schema := range apiContext.Schemas.SchemasForVersion(*apiContext.Version) {
if strings.EqualFold(schema.ID, id) {
schemaData := map[string]interface{}{}
data, err := json.Marshal(s.modifyForAccessControl(apiContext, *schema))
if err != nil {
return nil, err
}
return schemaData, json.Unmarshal(data, &schemaData)
}
}
return nil, httperror.NewAPIError(httperror.NotFound, "no such schema")
}
func (s *Store) modifyForAccessControl(context *types.APIContext, schema types.Schema) *types.Schema {
var resourceMethods []string
if slice.ContainsString(schema.ResourceMethods, http.MethodPut) && schema.CanUpdate(context) == nil {
resourceMethods = append(resourceMethods, http.MethodPut)
}
if slice.ContainsString(schema.ResourceMethods, http.MethodDelete) && schema.CanDelete(context) == nil {
resourceMethods = append(resourceMethods, http.MethodDelete)
}
if slice.ContainsString(schema.ResourceMethods, http.MethodGet) && schema.CanGet(context) == nil {
resourceMethods = append(resourceMethods, http.MethodGet)
}
var collectionMethods []string
if slice.ContainsString(schema.CollectionMethods, http.MethodPost) && schema.CanCreate(context) == nil {
collectionMethods = append(collectionMethods, http.MethodPost)
}
if slice.ContainsString(schema.CollectionMethods, http.MethodGet) && schema.CanList(context) == nil {
collectionMethods = append(collectionMethods, http.MethodGet)
}
schema.ResourceMethods = resourceMethods
schema.CollectionMethods = collectionMethods
return &schema
}
func (s *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
return nil, nil
}
func (s *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
schemaMap := apiContext.Schemas.SchemasForVersion(*apiContext.Version)
schemas := make([]*types.Schema, 0, len(schemaMap))
schemaData := make([]map[string]interface{}, 0, len(schemaMap))
included := map[string]bool{}
for _, schema := range schemaMap {
if included[schema.ID] {
continue
}
if schema.CanList(apiContext) == nil || schema.CanGet(apiContext) == nil {
schemas = s.addSchema(apiContext, schema, schemaMap, schemas, included)
}
}
data, err := json.Marshal(schemas)
if err != nil {
return nil, err
}
return schemaData, json.Unmarshal(data, &schemaData)
}
func (s *Store) addSchema(apiContext *types.APIContext, schema *types.Schema, schemaMap map[string]*types.Schema, schemas []*types.Schema, included map[string]bool) []*types.Schema {
included[schema.ID] = true
schemas = s.traverseAndAdd(apiContext, schema, schemaMap, schemas, included)
schemas = append(schemas, s.modifyForAccessControl(apiContext, *schema))
return schemas
}
func (s *Store) traverseAndAdd(apiContext *types.APIContext, schema *types.Schema, schemaMap map[string]*types.Schema, schemas []*types.Schema, included map[string]bool) []*types.Schema {
for _, field := range schema.ResourceFields {
t := ""
subType := field.Type
for subType != t {
t = subType
subType = definition.SubType(t)
}
if refSchema, ok := schemaMap[t]; ok && !included[t] {
schemas = s.addSchema(apiContext, refSchema, schemaMap, schemas, included)
}
}
for _, action := range schema.ResourceActions {
for _, t := range []string{action.Output, action.Input} {
if t == "" {
continue
}
if refSchema, ok := schemaMap[t]; ok && !included[t] {
schemas = s.addSchema(apiContext, refSchema, schemaMap, schemas, included)
}
}
}
for _, action := range schema.CollectionActions {
for _, t := range []string{action.Output, action.Input} {
if t == "" {
continue
}
if refSchema, ok := schemaMap[t]; ok && !included[t] {
schemas = s.addSchema(apiContext, refSchema, schemaMap, schemas, included)
}
}
}
return schemas
}

View File

@@ -1,45 +0,0 @@
package subtype
import (
"strings"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
)
type Store struct {
types.Store
subType string
}
func NewSubTypeStore(subType string, store types.Store) *Store {
return &Store{
Store: store,
subType: subType,
}
}
func (p *Store) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
if data != nil {
data["kind"] = p.subType
}
return p.Store.Create(apiContext, schema, data)
}
func (p *Store) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
if data != nil {
data["kind"] = convert.Uncapitalize(strings.Replace(p.subType, "namespaced", "", 1))
data["type"] = data["kind"]
}
return p.Store.Update(apiContext, schema, data, id)
}
func (p *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
opt.Conditions = append(opt.Conditions, types.NewConditionFromString("type", types.ModifierEQ, p.subType))
return p.Store.List(apiContext, schema, opt)
}
func (p *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
opt.Conditions = append(opt.Conditions, types.NewConditionFromString("type", types.ModifierEQ, p.subType))
return p.Store.Watch(apiContext, schema, opt)
}

View File

@@ -1,122 +0,0 @@
package transform
import (
"fmt"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
)
type TransformerFunc func(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, opt *types.QueryOptions) (map[string]interface{}, error)
type ListTransformerFunc func(apiContext *types.APIContext, schema *types.Schema, data []map[string]interface{}, opt *types.QueryOptions) ([]map[string]interface{}, error)
type StreamTransformerFunc func(apiContext *types.APIContext, schema *types.Schema, data chan map[string]interface{}, opt *types.QueryOptions) (chan map[string]interface{}, error)
type Store struct {
Store types.Store
Transformer TransformerFunc
ListTransformer ListTransformerFunc
StreamTransformer StreamTransformerFunc
}
func (s *Store) Context() types.StorageContext {
return s.Store.Context()
}
func (s *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
data, err := s.Store.ByID(apiContext, schema, id)
if err != nil {
return nil, err
}
if s.Transformer == nil {
return data, nil
}
obj, err := s.Transformer(apiContext, schema, data, &types.QueryOptions{
Options: map[string]string{
"ByID": "true",
},
})
if obj == nil && err == nil {
return obj, httperror.NewAPIError(httperror.NotFound, fmt.Sprintf("%s not found", id))
}
return obj, err
}
func (s *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
c, err := s.Store.Watch(apiContext, schema, opt)
if err != nil {
return nil, err
}
if s.StreamTransformer != nil {
return s.StreamTransformer(apiContext, schema, c, opt)
}
return convert.Chan(c, func(data map[string]interface{}) map[string]interface{} {
item, err := s.Transformer(apiContext, schema, data, opt)
if err != nil {
return nil
}
return item
}), nil
}
func (s *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
data, err := s.Store.List(apiContext, schema, opt)
if err != nil {
return nil, err
}
if s.ListTransformer != nil {
return s.ListTransformer(apiContext, schema, data, opt)
}
if s.Transformer == nil {
return data, nil
}
var result []map[string]interface{}
for _, item := range data {
item, err := s.Transformer(apiContext, schema, item, opt)
if err != nil {
return nil, err
}
if item != nil {
result = append(result, item)
}
}
return result, nil
}
func (s *Store) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
data, err := s.Store.Create(apiContext, schema, data)
if err != nil {
return nil, err
}
if s.Transformer == nil {
return data, nil
}
return s.Transformer(apiContext, schema, data, nil)
}
func (s *Store) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
data, err := s.Store.Update(apiContext, schema, data, id)
if err != nil {
return nil, err
}
if s.Transformer == nil {
return data, nil
}
return s.Transformer(apiContext, schema, data, nil)
}
func (s *Store) Delete(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
obj, err := s.Store.Delete(apiContext, schema, id)
if err != nil || obj == nil {
return obj, err
}
return s.Transformer(apiContext, schema, obj, nil)
}

View File

@@ -1,115 +0,0 @@
package wrapper
import (
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
)
func Wrap(store types.Store) types.Store {
return &StoreWrapper{
store: store,
}
}
type StoreWrapper struct {
store types.Store
}
func (s *StoreWrapper) Context() types.StorageContext {
return s.store.Context()
}
func (s *StoreWrapper) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
data, err := s.store.ByID(apiContext, schema, id)
if err != nil {
return nil, err
}
return apiContext.FilterObject(&types.QueryOptions{
Conditions: apiContext.SubContextAttributeProvider.Query(apiContext, schema),
}, schema, data), nil
}
func (s *StoreWrapper) List(apiContext *types.APIContext, schema *types.Schema, opts *types.QueryOptions) ([]map[string]interface{}, error) {
opts.Conditions = append(opts.Conditions, apiContext.SubContextAttributeProvider.Query(apiContext, schema)...)
data, err := s.store.List(apiContext, schema, opts)
if err != nil {
return nil, err
}
return apiContext.FilterList(opts, schema, data), nil
}
func (s *StoreWrapper) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
c, err := s.store.Watch(apiContext, schema, opt)
if err != nil || c == nil {
return nil, err
}
return convert.Chan(c, func(data map[string]interface{}) map[string]interface{} {
return apiContext.FilterObject(&types.QueryOptions{
Conditions: apiContext.SubContextAttributeProvider.Query(apiContext, schema),
}, schema, data)
}), nil
}
func (s *StoreWrapper) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
for key, value := range apiContext.SubContextAttributeProvider.Create(apiContext, schema) {
if data == nil {
data = map[string]interface{}{}
}
data[key] = value
}
data, err := s.store.Create(apiContext, schema, data)
if err != nil {
return nil, err
}
return data, nil
}
func (s *StoreWrapper) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
err := validateGet(apiContext, schema, id)
if err != nil {
return nil, err
}
data, err = s.store.Update(apiContext, schema, data, id)
if err != nil {
return nil, err
}
return apiContext.FilterObject(&types.QueryOptions{
Conditions: apiContext.SubContextAttributeProvider.Query(apiContext, schema),
}, schema, data), nil
}
func (s *StoreWrapper) Delete(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
if err := validateGet(apiContext, schema, id); err != nil {
return nil, err
}
return s.store.Delete(apiContext, schema, id)
}
func validateGet(apiContext *types.APIContext, schema *types.Schema, id string) error {
store := schema.Store
if store == nil {
return nil
}
existing, err := store.ByID(apiContext, schema, id)
if err != nil {
return err
}
if apiContext.Filter(&types.QueryOptions{
Conditions: apiContext.SubContextAttributeProvider.Query(apiContext, schema),
}, schema, existing) == nil {
return httperror.NewAPIError(httperror.NotFound, "failed to find "+id)
}
return nil
}

View File

@@ -1,121 +0,0 @@
package types
import (
"github.com/rancher/norman/types/convert"
)
var (
CondEQ = QueryConditionType{ModifierEQ, 1}
CondNE = QueryConditionType{ModifierNE, 1}
CondNull = QueryConditionType{ModifierNull, 0}
CondNotNull = QueryConditionType{ModifierNotNull, 0}
CondIn = QueryConditionType{ModifierIn, -1}
CondNotIn = QueryConditionType{ModifierNotIn, -1}
CondOr = QueryConditionType{ModifierType("or"), 1}
CondAnd = QueryConditionType{ModifierType("and"), 1}
mods = map[ModifierType]QueryConditionType{
CondEQ.Name: CondEQ,
CondNE.Name: CondNE,
CondNull.Name: CondNull,
CondNotNull.Name: CondNotNull,
CondIn.Name: CondIn,
CondNotIn.Name: CondNotIn,
CondOr.Name: CondOr,
CondAnd.Name: CondAnd,
}
)
type QueryConditionType struct {
Name ModifierType
Args int
}
type QueryCondition struct {
Field string
Value string
Values map[string]bool
conditionType QueryConditionType
left, right *QueryCondition
}
func (q *QueryCondition) Valid(schema *Schema, data map[string]interface{}) bool {
switch q.conditionType {
case CondAnd:
if q.left == nil || q.right == nil {
return false
}
return q.left.Valid(schema, data) && q.right.Valid(schema, data)
case CondOr:
if q.left == nil || q.right == nil {
return false
}
return q.left.Valid(schema, data) || q.right.Valid(schema, data)
case CondEQ:
return q.Value == convert.ToString(valueOrDefault(schema, data, q))
case CondNE:
return q.Value != convert.ToString(valueOrDefault(schema, data, q))
case CondIn:
return q.Values[convert.ToString(valueOrDefault(schema, data, q))]
case CondNotIn:
return !q.Values[convert.ToString(valueOrDefault(schema, data, q))]
case CondNotNull:
return convert.ToString(valueOrDefault(schema, data, q)) != ""
case CondNull:
return convert.ToString(valueOrDefault(schema, data, q)) == ""
}
return false
}
func valueOrDefault(schema *Schema, data map[string]interface{}, q *QueryCondition) interface{} {
value := data[q.Field]
if value == nil {
value = schema.ResourceFields[q.Field].Default
}
return value
}
func (q *QueryCondition) ToCondition() Condition {
cond := Condition{
Modifier: q.conditionType.Name,
}
if q.conditionType.Args == 1 {
cond.Value = q.Value
} else if q.conditionType.Args == -1 {
stringValues := []string{}
for val := range q.Values {
stringValues = append(stringValues, val)
}
cond.Value = stringValues
}
return cond
}
func ValidMod(mod ModifierType) bool {
_, ok := mods[mod]
return ok
}
func EQ(key, value string) *QueryCondition {
return NewConditionFromString(key, ModifierEQ, value)
}
func NewConditionFromString(field string, mod ModifierType, values ...string) *QueryCondition {
q := &QueryCondition{
Field: field,
conditionType: mods[mod],
Values: map[string]bool{},
}
for i, value := range values {
if i == 0 {
q.Value = value
}
q.Values[value] = true
}
return q
}

View File

@@ -1,254 +0,0 @@
package convert
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"time"
"unicode"
)
func Chan(c <-chan map[string]interface{}, f func(map[string]interface{}) map[string]interface{}) chan map[string]interface{} {
if c == nil {
return nil
}
result := make(chan map[string]interface{})
go func() {
for data := range c {
modified := f(data)
if modified != nil {
result <- modified
}
}
close(result)
}()
return result
}
func Singular(value interface{}) interface{} {
if slice, ok := value.([]string); ok {
if len(slice) == 0 {
return nil
}
return slice[0]
}
if slice, ok := value.([]interface{}); ok {
if len(slice) == 0 {
return nil
}
return slice[0]
}
return value
}
func ToStringNoTrim(value interface{}) string {
if t, ok := value.(time.Time); ok {
return t.Format(time.RFC3339)
}
single := Singular(value)
if single == nil {
return ""
}
return fmt.Sprint(single)
}
func ToString(value interface{}) string {
return strings.TrimSpace(ToStringNoTrim(value))
}
func ToTimestamp(value interface{}) (int64, error) {
str := ToString(value)
if str == "" {
return 0, errors.New("invalid date")
}
t, err := time.Parse(time.RFC3339, str)
if err != nil {
return 0, err
}
return t.UnixNano() / 1000000, nil
}
func ToBool(value interface{}) bool {
value = Singular(value)
b, ok := value.(bool)
if ok {
return b
}
str := strings.ToLower(ToString(value))
return str == "true" || str == "t" || str == "yes" || str == "y"
}
func ToNumber(value interface{}) (int64, error) {
value = Singular(value)
i, ok := value.(int64)
if ok {
return i, nil
}
f, ok := value.(float64)
if ok {
return int64(f), nil
}
if n, ok := value.(json.Number); ok {
i, err := n.Int64()
if err == nil {
return i, nil
}
f, err := n.Float64()
return int64(f), err
}
return strconv.ParseInt(ToString(value), 10, 64)
}
func Capitalize(s string) string {
if len(s) <= 1 {
return strings.ToUpper(s)
}
return strings.ToUpper(s[:1]) + s[1:]
}
func Uncapitalize(s string) string {
if len(s) <= 1 {
return strings.ToLower(s)
}
return strings.ToLower(s[:1]) + s[1:]
}
func LowerTitle(input string) string {
runes := []rune(input)
for i := 0; i < len(runes); i++ {
if unicode.IsUpper(runes[i]) &&
(i == 0 ||
i == len(runes)-1 ||
unicode.IsUpper(runes[i+1])) {
runes[i] = unicode.ToLower(runes[i])
} else {
break
}
}
return string(runes)
}
func IsAPIObjectEmpty(v interface{}) bool {
if v == nil || v == "" || v == 0 || v == false {
return true
}
if m, ok := v.(map[string]interface{}); ok {
return len(m) == 0
}
if s, ok := v.([]interface{}); ok {
return len(s) == 0
}
return false
}
func ToMapInterface(obj interface{}) map[string]interface{} {
v, _ := obj.(map[string]interface{})
return v
}
func ToInterfaceSlice(obj interface{}) []interface{} {
if v, ok := obj.([]interface{}); ok {
return v
}
return nil
}
func ToMapSlice(obj interface{}) []map[string]interface{} {
if v, ok := obj.([]map[string]interface{}); ok {
return v
}
vs, _ := obj.([]interface{})
var result []map[string]interface{}
for _, item := range vs {
if v, ok := item.(map[string]interface{}); ok {
result = append(result, v)
} else {
return nil
}
}
return result
}
func ToStringSlice(data interface{}) []string {
if v, ok := data.([]string); ok {
return v
}
if v, ok := data.([]interface{}); ok {
var result []string
for _, item := range v {
result = append(result, ToString(item))
}
return result
}
return nil
}
func ToObj(data interface{}, into interface{}) error {
bytes, err := json.Marshal(data)
if err != nil {
return err
}
return json.Unmarshal(bytes, into)
}
func EncodeToMap(obj interface{}) (map[string]interface{}, error) {
if m, ok := obj.(map[string]interface{}); ok {
return m, nil
}
b, err := json.Marshal(obj)
if err != nil {
return nil, err
}
result := map[string]interface{}{}
dec := json.NewDecoder(bytes.NewBuffer(b))
dec.UseNumber()
return result, dec.Decode(&result)
}
func ToJSONKey(str string) string {
parts := strings.Split(str, "_")
for i := 1; i < len(parts); i++ {
parts[i] = strings.Title(parts[i])
}
return strings.Join(parts, "")
}
func ToYAMLKey(str string) string {
var result []rune
cap := false
for i, r := range []rune(str) {
if i == 0 {
if unicode.IsUpper(r) {
cap = true
}
result = append(result, unicode.ToLower(r))
continue
}
if unicode.IsUpper(r) {
if cap {
result = append(result, unicode.ToLower(r))
} else {
result = append(result, '_', unicode.ToLower(r))
}
} else {
cap = false
result = append(result, r)
}
}
return string(result)
}

View File

@@ -1,156 +0,0 @@
package merge
import (
"strings"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/definition"
)
func APIUpdateMerge(schema *types.Schema, schemas *types.Schemas, dest, src map[string]interface{}, replace bool) map[string]interface{} {
result := UpdateMerge(schema, schemas, dest, src, replace)
if s, ok := dest["status"]; ok {
result["status"] = s
}
if m, ok := dest["metadata"]; ok {
result["metadata"] = mergeMetadata(convert.ToMapInterface(m), convert.ToMapInterface(src["metadata"]))
}
return result
}
func UpdateMerge(schema *types.Schema, schemas *types.Schemas, dest, src map[string]interface{}, replace bool) map[string]interface{} {
return mergeMaps("", nil, schema, schemas, replace, dest, src)
}
func isProtected(k string) bool {
if !strings.Contains(k, "cattle.io/") || (isField(k) && k != "field.cattle.io/creatorId") {
return false
}
return true
}
func isField(k string) bool {
return strings.HasPrefix(k, "field.cattle.io/")
}
func mergeProtected(dest, src map[string]interface{}) map[string]interface{} {
if src == nil {
return dest
}
result := copyMap(dest)
for k, v := range src {
if isProtected(k) {
continue
}
result[k] = v
}
for k := range dest {
if isProtected(k) || isField(k) {
continue
}
if _, ok := src[k]; !ok {
delete(result, k)
}
}
return result
}
func mergeMetadata(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
result := copyMap(dest)
labels := convert.ToMapInterface(dest["labels"])
srcLabels := convert.ToMapInterface(src["labels"])
labels = mergeProtected(labels, srcLabels)
annotations := convert.ToMapInterface(dest["annotations"])
srcAnnotation := convert.ToMapInterface(src["annotations"])
annotations = mergeProtected(annotations, srcAnnotation)
result["labels"] = labels
result["annotations"] = annotations
return result
}
func merge(field, fieldType string, parentSchema, schema *types.Schema, schemas *types.Schemas, replace bool, dest, src interface{}) interface{} {
if isMap(field, schema, schemas) {
return src
}
sm, smOk := src.(map[string]interface{})
dm, dmOk := dest.(map[string]interface{})
if smOk && dmOk {
fieldType, fieldSchema := getSchema(field, fieldType, parentSchema, schema, schemas)
return mergeMaps(fieldType, schema, fieldSchema, schemas, replace, dm, sm)
}
return src
}
func getSchema(field, parentFieldType string, parentSchema, schema *types.Schema, schemas *types.Schemas) (string, *types.Schema) {
if schema == nil {
if definition.IsMapType(parentFieldType) && parentSchema != nil {
subType := definition.SubType(parentFieldType)
s := schemas.Schema(&parentSchema.Version, subType)
if s != nil && s.InternalSchema != nil {
s = s.InternalSchema
}
return subType, s
}
return "", nil
}
fieldType := schema.ResourceFields[field].Type
s := schemas.Schema(&schema.Version, fieldType)
if s != nil && s.InternalSchema != nil {
return fieldType, s.InternalSchema
}
return fieldType, s
}
func isMap(field string, schema *types.Schema, schemas *types.Schemas) bool {
if schema == nil {
return false
}
f := schema.ResourceFields[field]
mapType := definition.IsMapType(f.Type)
if !mapType {
return false
}
subType := definition.SubType(f.Type)
return schemas.Schema(&schema.Version, subType) == nil
}
func mergeMaps(fieldType string, parentSchema, schema *types.Schema, schemas *types.Schemas, replace bool, dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
result := copyMapReplace(schema, dest, replace)
for k, v := range src {
result[k] = merge(k, fieldType, parentSchema, schema, schemas, replace, dest[k], v)
}
return result
}
func copyMap(src map[string]interface{}) map[string]interface{} {
result := map[string]interface{}{}
for k, v := range src {
result[k] = v
}
return result
}
func copyMapReplace(schema *types.Schema, src map[string]interface{}, replace bool) map[string]interface{} {
result := map[string]interface{}{}
for k, v := range src {
if replace && schema != nil {
f := schema.ResourceFields[k]
if f.Update {
continue
}
}
result[k] = v
}
return result
}

View File

@@ -1,11 +0,0 @@
package convert
import "fmt"
func ToReference(typeName string) string {
return fmt.Sprintf("reference[%s]", typeName)
}
func ToFullReference(path, typeName string) string {
return fmt.Sprintf("reference[%s/schemas/%s]", path, typeName)
}

View File

@@ -1,29 +0,0 @@
package schemaconvert
import (
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
)
func InternalToInternal(from interface{}, fromSchema *types.Schema, toSchema *types.Schema, target interface{}) error {
data, err := convert.EncodeToMap(from)
if err != nil {
return err
}
fromSchema.Mapper.FromInternal(data)
if err := toSchema.Mapper.ToInternal(data); err != nil {
return err
}
return convert.ToObj(data, target)
}
func ToInternal(from interface{}, schema *types.Schema, target interface{}) error {
data, err := convert.EncodeToMap(from)
if err != nil {
return err
}
if err := schema.Mapper.ToInternal(data); err != nil {
return err
}
return convert.ToObj(data, target)
}

View File

@@ -1,45 +0,0 @@
package convert
const (
ArrayKey = "{ARRAY}"
MapKey = "{MAP}"
)
type TransformerFunc func(input interface{}) interface{}
func Transform(data map[string]interface{}, path []string, transformer TransformerFunc) {
if len(path) == 0 || len(data) == 0 {
return
}
key := path[0]
path = path[1:]
value := data[key]
if value == nil {
return
}
if len(path) == 0 {
data[key] = transformer(value)
return
}
// You can't end a path with ARRAY/MAP. Not supported right now
if len(path) > 1 {
switch path[0] {
case ArrayKey:
for _, valueMap := range ToMapSlice(value) {
Transform(valueMap, path[1:], transformer)
}
return
case MapKey:
for _, valueMap := range ToMapInterface(value) {
Transform(ToMapInterface(valueMap), path[1:], transformer)
}
return
}
}
Transform(ToMapInterface(value), path, transformer)
}

View File

@@ -1,18 +0,0 @@
package convert
import (
"regexp"
"strings"
)
var (
splitRegexp = regexp.MustCompile("[[:space:]]*,[[:space:]]*")
)
func ToValuesSlice(value string) []string {
value = strings.TrimSpace(value)
if strings.HasPrefix(value, "(") && strings.HasSuffix(value, ")") {
return splitRegexp.Split(value[1:len(value)-1], -1)
}
return []string{value}
}

View File

@@ -1,45 +0,0 @@
package definition
import (
"strings"
"github.com/rancher/norman/types/convert"
)
func IsMapType(fieldType string) bool {
return strings.HasPrefix(fieldType, "map[") && strings.HasSuffix(fieldType, "]")
}
func IsArrayType(fieldType string) bool {
return strings.HasPrefix(fieldType, "array[") && strings.HasSuffix(fieldType, "]")
}
func IsReferenceType(fieldType string) bool {
return strings.HasPrefix(fieldType, "reference[") && strings.HasSuffix(fieldType, "]")
}
func HasReferenceType(fieldType string) bool {
return strings.Contains(fieldType, "reference[")
}
func SubType(fieldType string) string {
i := strings.Index(fieldType, "[")
if i <= 0 || i >= len(fieldType)-1 {
return fieldType
}
return fieldType[i+1 : len(fieldType)-1]
}
func GetType(data map[string]interface{}) string {
return GetShortTypeFromFull(GetFullType(data))
}
func GetShortTypeFromFull(fullType string) string {
parts := strings.Split(fullType, "/")
return parts[len(parts)-1]
}
func GetFullType(data map[string]interface{}) string {
return convert.ToString(data["type"])
}

View File

@@ -1,32 +0,0 @@
package types
import (
"encoding/json"
"io"
"regexp"
"github.com/ghodss/yaml"
)
var (
commenter = regexp.MustCompile("(?m)^( *)zzz#\\((.*)\\)\\((.*)\\)([a-z]+.*):(.*)")
)
func JSONEncoder(writer io.Writer, v interface{}) error {
return json.NewEncoder(writer).Encode(v)
}
func YAMLEncoder(writer io.Writer, v interface{}) error {
data, err := json.Marshal(v)
if err != nil {
return err
}
buf, err := yaml.JSONToYAML(data)
if err != nil {
return err
}
//buf = commenter.ReplaceAll(buf, []byte("${1}# ${2}type: ${3}\n${1}# ${4}:${5}"))
buf = commenter.ReplaceAll(buf, []byte("${1}# ${4}:${5}"))
_, err = writer.Write(buf)
return err
}

View File

@@ -1,23 +0,0 @@
package factory
import (
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/mapper"
"k8s.io/apimachinery/pkg/apis/meta/v1"
)
func Schemas(version *types.APIVersion) *types.Schemas {
s := types.NewSchemas()
s.DefaultMappers = func() []types.Mapper {
return []types.Mapper{
mapper.NewObject(),
}
}
s.DefaultPostMappers = func() []types.Mapper {
return []types.Mapper{
&mapper.RenameReference{},
}
}
s.AddMapperForType(version, v1.ObjectMeta{}, mapper.NewMetadataMapper())
return s
}

View File

@@ -1,19 +0,0 @@
package types
import (
"fmt"
"regexp"
"strings"
utilrand "k8s.io/apimachinery/pkg/util/rand"
)
var (
lowerChars = regexp.MustCompile("[a-z]+")
)
func GenerateName(typeName string) string {
base := typeName[0:1] + lowerChars.ReplaceAllString(typeName[1:], "")
last := utilrand.String(5)
return fmt.Sprintf("%s-%s", strings.ToLower(base), last)
}

View File

@@ -1,178 +0,0 @@
package types
import (
"fmt"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/definition"
"github.com/rancher/norman/types/values"
)
type Mapper interface {
FromInternal(data map[string]interface{})
ToInternal(data map[string]interface{}) error
ModifySchema(schema *Schema, schemas *Schemas) error
}
type Mappers []Mapper
func (m Mappers) FromInternal(data map[string]interface{}) {
for _, mapper := range m {
mapper.FromInternal(data)
}
}
func (m Mappers) ToInternal(data map[string]interface{}) error {
var errors []error
for i := len(m) - 1; i >= 0; i-- {
errors = append(errors, m[i].ToInternal(data))
}
return NewErrors(errors...)
}
func (m Mappers) ModifySchema(schema *Schema, schemas *Schemas) error {
for _, mapper := range m {
if err := mapper.ModifySchema(schema, schemas); err != nil {
return err
}
}
return nil
}
type typeMapper struct {
Mappers []Mapper
root bool
typeName string
subSchemas map[string]*Schema
subArraySchemas map[string]*Schema
subMapSchemas map[string]*Schema
}
func (t *typeMapper) FromInternal(data map[string]interface{}) {
name, _ := values.GetValueN(data, "metadata", "name").(string)
namespace, _ := values.GetValueN(data, "metadata", "namespace").(string)
for fieldName, schema := range t.subSchemas {
if schema.Mapper == nil {
continue
}
fieldData, _ := data[fieldName].(map[string]interface{})
schema.Mapper.FromInternal(fieldData)
}
for fieldName, schema := range t.subMapSchemas {
if schema.Mapper == nil {
continue
}
datas, _ := data[fieldName].(map[string]interface{})
for _, fieldData := range datas {
mapFieldData, _ := fieldData.(map[string]interface{})
schema.Mapper.FromInternal(mapFieldData)
}
}
for fieldName, schema := range t.subArraySchemas {
if schema.Mapper == nil {
continue
}
datas, _ := data[fieldName].([]interface{})
for _, fieldData := range datas {
mapFieldData, _ := fieldData.(map[string]interface{})
schema.Mapper.FromInternal(mapFieldData)
}
}
if _, ok := data["type"]; !ok && data != nil {
data["type"] = t.typeName
}
Mappers(t.Mappers).FromInternal(data)
if data != nil && t.root {
if _, ok := data["id"]; ok {
if namespace != "" {
id, _ := data["id"].(string)
data["id"] = namespace + ":" + id
}
} else {
if name != "" {
if namespace == "" {
data["id"] = name
} else {
data["id"] = namespace + ":" + name
}
}
}
}
if _, ok := data["type"]; !ok && data != nil {
if _, ok := data["id"]; ok {
data["type"] = t.typeName
}
}
}
func (t *typeMapper) ToInternal(data map[string]interface{}) error {
errors := Errors{}
errors.Add(Mappers(t.Mappers).ToInternal(data))
for fieldName, schema := range t.subArraySchemas {
if schema.Mapper == nil {
continue
}
datas, _ := data[fieldName].([]interface{})
for _, fieldData := range datas {
errors.Add(schema.Mapper.ToInternal(convert.ToMapInterface(fieldData)))
}
}
for fieldName, schema := range t.subMapSchemas {
if schema.Mapper == nil {
continue
}
datas, _ := data[fieldName].(map[string]interface{})
for _, fieldData := range datas {
errors.Add(schema.Mapper.ToInternal(convert.ToMapInterface(fieldData)))
}
}
for fieldName, schema := range t.subSchemas {
if schema.Mapper == nil {
continue
}
fieldData, _ := data[fieldName].(map[string]interface{})
errors.Add(schema.Mapper.ToInternal(fieldData))
}
return errors.Err()
}
func (t *typeMapper) ModifySchema(schema *Schema, schemas *Schemas) error {
t.subSchemas = map[string]*Schema{}
t.subArraySchemas = map[string]*Schema{}
t.subMapSchemas = map[string]*Schema{}
t.typeName = fmt.Sprintf("%s/schemas/%s", schema.Version.Path, schema.ID)
mapperSchema := schema
if schema.InternalSchema != nil {
mapperSchema = schema.InternalSchema
}
for name, field := range mapperSchema.ResourceFields {
fieldType := field.Type
targetMap := t.subSchemas
if definition.IsArrayType(fieldType) {
fieldType = definition.SubType(fieldType)
targetMap = t.subArraySchemas
} else if definition.IsMapType(fieldType) {
fieldType = definition.SubType(fieldType)
targetMap = t.subMapSchemas
}
schema := schemas.Schema(&schema.Version, fieldType)
if schema != nil {
targetMap[name] = schema
}
}
return Mappers(t.Mappers).ModifySchema(schema, schemas)
}

View File

@@ -1,38 +0,0 @@
package mapper
import (
"strings"
"github.com/rancher/norman/types"
)
type Access struct {
Fields map[string]string
Optional bool
}
func (e Access) FromInternal(data map[string]interface{}) {
}
func (e Access) ToInternal(data map[string]interface{}) error {
return nil
}
func (e Access) ModifySchema(schema *types.Schema, schemas *types.Schemas) error {
for name, access := range e.Fields {
if err := ValidateField(name, schema); err != nil {
if e.Optional {
continue
}
return err
}
field := schema.ResourceFields[name]
field.Create = strings.Contains(access, "c")
field.Update = strings.Contains(access, "u")
field.WriteOnly = strings.Contains(access, "o")
schema.ResourceFields[name] = field
}
return nil
}

View File

@@ -1,58 +0,0 @@
package mapper
import (
"encoding/json"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/values"
)
type AnnotationField struct {
Field string
Object bool
List bool
IgnoreDefinition bool
}
func (e AnnotationField) FromInternal(data map[string]interface{}) {
v, ok := values.RemoveValue(data, "annotations", "field.cattle.io/"+e.Field)
if ok {
if e.Object {
data := map[string]interface{}{}
//ignore error
if err := json.Unmarshal([]byte(convert.ToString(v)), &data); err == nil {
v = data
}
}
if e.List {
var data []interface{}
if err := json.Unmarshal([]byte(convert.ToString(v)), &data); err == nil {
v = data
}
}
data[e.Field] = v
}
}
func (e AnnotationField) ToInternal(data map[string]interface{}) error {
v, ok := data[e.Field]
if ok {
if e.Object || e.List {
if bytes, err := json.Marshal(v); err == nil {
v = string(bytes)
}
}
values.PutValue(data, convert.ToString(v), "annotations", "field.cattle.io/"+e.Field)
}
values.RemoveValue(data, e.Field)
return nil
}
func (e AnnotationField) ModifySchema(schema *types.Schema, schemas *types.Schemas) error {
if e.IgnoreDefinition {
return nil
}
return ValidateField(e.Field, schema)
}

View File

@@ -1,35 +0,0 @@
package mapper
import (
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
)
type APIGroup struct {
apiVersion string
kind string
}
func (a *APIGroup) FromInternal(data map[string]interface{}) {
}
func (a *APIGroup) ToInternal(data map[string]interface{}) error {
_, ok := data["apiVersion"]
if !ok && data != nil {
data["apiVersion"] = a.apiVersion
}
_, ok = data["kind"]
if !ok && data != nil {
data["kind"] = a.kind
}
return nil
}
func (a *APIGroup) ModifySchema(schema *types.Schema, schemas *types.Schemas) error {
a.apiVersion = schema.Version.Group + "/" + schema.Version.Version
a.kind = convert.Capitalize(schema.ID)
return nil
}

View File

@@ -1,63 +0,0 @@
package mapper
import (
"encoding/base64"
"strings"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/values"
)
type Base64 struct {
Field string
IgnoreDefinition bool
Separator string
}
func (m Base64) FromInternal(data map[string]interface{}) {
if v, ok := values.RemoveValue(data, strings.Split(m.Field, m.getSep())...); ok {
str := convert.ToString(v)
if str == "" {
return
}
newData, err := base64.StdEncoding.DecodeString(str)
if err != nil {
log.Errorf("failed to base64 decode data")
}
values.PutValue(data, string(newData), strings.Split(m.Field, m.getSep())...)
}
}
func (m Base64) ToInternal(data map[string]interface{}) error {
if v, ok := values.RemoveValue(data, strings.Split(m.Field, m.getSep())...); ok {
str := convert.ToString(v)
if str == "" {
return nil
}
newData := base64.StdEncoding.EncodeToString([]byte(str))
values.PutValue(data, newData, strings.Split(m.Field, m.getSep())...)
}
return nil
}
func (m Base64) ModifySchema(s *types.Schema, schemas *types.Schemas) error {
if !m.IgnoreDefinition {
if err := ValidateField(m.Field, s); err != nil {
return err
}
}
return nil
}
func (m Base64) getSep() string {
if m.Separator == "" {
return "/"
}
return m.Separator
}

View File

@@ -1,48 +0,0 @@
package mapper
import (
"path"
"github.com/rancher/norman/types"
)
type BatchMove struct {
From []string
To string
DestDefined bool
NoDeleteFromField bool
moves []Move
}
func (b *BatchMove) FromInternal(data map[string]interface{}) {
for _, m := range b.moves {
m.FromInternal(data)
}
}
func (b *BatchMove) ToInternal(data map[string]interface{}) error {
errors := types.Errors{}
for i := len(b.moves) - 1; i >= 0; i-- {
errors.Add(b.moves[i].ToInternal(data))
}
return errors.Err()
}
func (b *BatchMove) ModifySchema(s *types.Schema, schemas *types.Schemas) error {
for _, from := range b.From {
b.moves = append(b.moves, Move{
From: from,
To: path.Join(b.To, from),
DestDefined: b.DestDefined,
NoDeleteFromField: b.NoDeleteFromField,
})
}
for _, m := range b.moves {
if err := m.ModifySchema(s, schemas); err != nil {
return err
}
}
return nil
}

View File

@@ -1,28 +0,0 @@
package mapper
import (
"github.com/rancher/norman/types"
)
type ChangeType struct {
Field string
Type string
}
func (c ChangeType) FromInternal(data map[string]interface{}) {
}
func (c ChangeType) ToInternal(data map[string]interface{}) error {
return nil
}
func (c ChangeType) ModifySchema(schema *types.Schema, schemas *types.Schemas) error {
if err := ValidateField(c.Field, schema); err != nil {
return err
}
f := schema.ResourceFields[c.Field]
f.Type = c.Type
schema.ResourceFields[c.Field] = f
return nil
}

View File

@@ -1,15 +0,0 @@
package mapper
import (
"fmt"
"github.com/rancher/norman/types"
)
func ValidateField(field string, schema *types.Schema) error {
if _, ok := schema.ResourceFields[field]; !ok {
return fmt.Errorf("field %s missing on schema %s", field, schema.ID)
}
return nil
}

View File

@@ -1,28 +0,0 @@
package mapper
import (
"github.com/rancher/norman/types"
)
type Condition struct {
Field string
Value interface{}
Mapper types.Mapper
}
func (m Condition) FromInternal(data map[string]interface{}) {
if data[m.Field] == m.Value {
m.Mapper.FromInternal(data)
}
}
func (m Condition) ToInternal(data map[string]interface{}) error {
if data[m.Field] == m.Value {
return m.Mapper.ToInternal(data)
}
return nil
}
func (m Condition) ModifySchema(s *types.Schema, schemas *types.Schemas) error {
return m.Mapper.ModifySchema(s, schemas)
}

View File

@@ -1,44 +0,0 @@
package mapper
import (
"fmt"
"github.com/rancher/norman/types"
)
type Copy struct {
From, To string
}
func (c Copy) FromInternal(data map[string]interface{}) {
if data == nil {
return
}
v, ok := data[c.From]
if ok {
data[c.To] = v
}
}
func (c Copy) ToInternal(data map[string]interface{}) error {
if data == nil {
return nil
}
t, tok := data[c.To]
_, fok := data[c.From]
if tok && !fok {
data[c.From] = t
}
return nil
}
func (c Copy) ModifySchema(s *types.Schema, schemas *types.Schemas) error {
f, ok := s.ResourceFields[c.From]
if !ok {
return fmt.Errorf("field %s missing on schema %s", c.From, s.ID)
}
s.ResourceFields[c.To] = f
return nil
}

View File

@@ -1,29 +0,0 @@
package mapper
import (
"github.com/rancher/norman/types"
)
var displayNameMappers = types.Mappers{
&Move{From: "name", To: "id"},
&Move{From: "displayName", To: "name"},
Access{Fields: map[string]string{
"id": "",
"name": "cru",
}},
}
type DisplayName struct {
}
func (d DisplayName) FromInternal(data map[string]interface{}) {
displayNameMappers.FromInternal(data)
}
func (d DisplayName) ToInternal(data map[string]interface{}) error {
return displayNameMappers.ToInternal(data)
}
func (d DisplayName) ModifySchema(schema *types.Schema, schemas *types.Schemas) error {
return displayNameMappers.ModifySchema(schema, schemas)
}

View File

@@ -1,31 +0,0 @@
package mapper
import (
"fmt"
"github.com/rancher/norman/types"
)
type Drop struct {
Field string
IgnoreDefinition bool
}
func (d Drop) FromInternal(data map[string]interface{}) {
delete(data, d.Field)
}
func (d Drop) ToInternal(data map[string]interface{}) error {
return nil
}
func (d Drop) ModifySchema(schema *types.Schema, schemas *types.Schemas) error {
if _, ok := schema.ResourceFields[d.Field]; !ok {
if !d.IgnoreDefinition {
return fmt.Errorf("can not drop missing field %s on %s", d.Field, schema.ID)
}
}
delete(schema.ResourceFields, d.Field)
return nil
}

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