Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
1055eed9
Commit
1055eed9
authored
Jul 27, 2015
by
Brendan Burns
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add initial storage types to the Kubernetes API
parent
68f90fd5
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
615 additions
and
1 deletion
+615
-1
extending-api.md
docs/design/extending-api.md
+222
-0
deep_copy_generated.go
pkg/api/deep_copy_generated.go
+69
-0
latest.go
pkg/api/latest/latest.go
+4
-1
register.go
pkg/api/register.go
+6
-0
types.go
pkg/api/types.go
+32
-0
conversion_generated.go
pkg/api/v1/conversion_generated.go
+152
-0
deep_copy_generated.go
pkg/api/v1/deep_copy_generated.go
+69
-0
register.go
pkg/api/v1/register.go
+6
-0
types.go
pkg/api/v1/types.go
+32
-0
validation.go
pkg/api/validation/validation.go
+23
-0
No files found.
docs/design/extending-api.md
0 → 100644
View file @
1055eed9
<!-- BEGIN MUNGE: UNVERSIONED_WARNING -->
<!-- BEGIN STRIP_FOR_RELEASE -->
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<h2>
PLEASE NOTE: This document applies to the HEAD of the source tree
</h2>
If you are using a released version of Kubernetes, you should
refer to the docs that go with that version.
<strong>
The latest 1.0.x release of this document can be found
[
here
](
http://releases.k8s.io/release-1.0/docs/design/extending-api.md
)
.
Documentation for other releases can be found at
[
releases.k8s.io
](
http://releases.k8s.io
)
.
</strong>
--
<!-- END STRIP_FOR_RELEASE -->
<!-- END MUNGE: UNVERSIONED_WARNING -->
# Adding custom resources to the Kubernetes API server
This document describes the design for implementing the storage of custom API types in the Kubernetes API Server.
## Resource Model
### The ThirdPartyResource
The
`ThirdPartyResource`
resource describes the multiple versions of a custom resource that the user wants to add
to the Kubernetes API.
`ThirdPartyResource`
is a non-namespaced resource, attempting to place it in a resource
will return an error.
Each
`ThirdPartyResource`
resource has the following:
*
Standard Kubernetes object metadata.
*
ResourceKind - The kind of the resources described by this third party resource.
*
Description - A free text description of the resource.
*
APIGroup - An API group that this resource should be placed into.
*
Versions - One or more
`Version`
objects.
### The `Version` Object
The
`Version`
object describes a single concrete version of a custom resource. The
`Version`
object currently
only specifies:
*
The
`Name`
of the version.
*
The
`APIGroup`
this version should belong to.
## Expectations about third party objects
Every object that is added to a third-party Kubernetes object store is expected to contain Kubernetes
compatible
[
object metadata
](
../devel/api-conventions.md#metadata
)
. This requirement enables the
Kubernetes API server to provide the following features:
*
Filtering lists of objects via LabelQueries
*
`resourceVersion`
-based optimistic concurrency via compare-and-swap
*
Versioned storage
*
Event recording
*
Integration with basic
`kubectl`
command line tooling.
*
Watch for resource changes.
The
`Kind`
for an instance of a third-party object (e.g. CronTab) below is expected to be
programnatically convertible to the name of the resource using
the following conversion. Kinds are expected to be of the form
`<CamelCaseKind>`
, the
`APIVersion`
for the object is expected to be
`<domain-name>/<api-group>/<api-version>`
.
For example
`example.com/stable/v1`
`domain-name`
is expected to be a fully qualified domain name.
'CamelCaseKind' is the specific type name.
To convert this into the
`metadata.name`
for the
`ThirdPartyResource`
resource instance,
the
`<domain-name>`
is copied verbatim, the
`CamelCaseKind`
is
then converted
using '-' instead of capitalization ('camel-case'), with the first character being assumed to be
capitalized. In pseudo code:
```
go
var
result
string
for
ix
:=
range
kindName
{
if
isCapital
(
kindName
[
ix
])
{
result
=
append
(
result
,
'-'
)
}
result
=
append
(
result
,
toLowerCase
(
kindName
[
ix
])
}
```
As a concrete example, the resource named
`camel-case-kind.example.com`
defines resources of Kind
`CamelCaseKind`
, in
the APIGroup with the prefix
`example.com/...`
.
The reason for this is to enable rapid lookup of a
`ThirdPartyResource`
object given the kind information.
This is also the reason why
`ThirdPartyResource`
is not namespaced.
## Usage
When a user creates a new
`ThirdPartyResource`
, the Kubernetes API Server reacts by creating a new, namespaced
RESTful resource path. For now, non-namespaced objects are not supported. As with existing built-in objects
deleting a namespace, deletes all third party resources in that namespace.
For example, if a user creates:
```
yaml
metadata
:
name
:
cron-tab.example.com
apiVersion
:
experimental/v1
kind
:
ThirdPartyResource
description
:
"
A
specification
of
a
Pod
to
run
on
a
cron
style
schedule"
versions
:
-
name
:
stable/v1
-
name
:
experimental/v2
```
Then the API server will program in two new RESTful resource paths:
*
`/thirdparty/example.com/stable/v1/namespaces/<namespace>/crontabs/...`
*
`/thirdparty/example.com/experimental/v2/namespaces/<namespace>/crontabs/...`
Now that this schema has been created, a user can
`POST`
:
```
json
{
"metadata"
:
{
"name"
:
"my-new-cron-object"
},
"apiVersion"
:
"example.com/stable/v1"
,
"kind"
:
"CronTab"
,
"cronSpec"
:
"* * * * /5"
,
"image"
:
"my-awesome-chron-image"
}
```
to:
`/third-party/example.com/stable/v1/namespaces/default/crontabs/my-new-cron-object`
and the corresponding data will be stored into etcd by the APIServer, so that when the user issues:
```
GET /third-party/example.com/stable/v1/namespaces/default/crontabs/my-new-cron-object`
```
And when they do that, they will get back the same data, but with additional Kubernetes metadata
(e.g.
`resourceVersion`
,
`createdTimestamp`
) filled in.
Likewise, to list all resources, a user can issue:
```
GET /third-party/example.com/stable/v1/namespaces/default/crontabs
```
and get back:
```
json
{
"apiVersion"
:
"example.com/stable/v1"
,
"kind"
:
"CronTabList"
,
"items"
:
[
{
"metadata"
:
{
"name"
:
"my-new-cron-object"
},
"apiVersion"
:
"example.com/stable/v1"
,
"kind"
:
"CronTab"
,
"cronSpec"
:
"* * * * /5"
,
"image"
:
"my-awesome-chron-image"
}
]
}
```
Because all objects are expected to contain standard Kubernetes metdata fileds, these
list operations can also use
`Label`
queries to filter requests down to specific subsets.
Likewise, clients can use watch endpoints to watch for changes to stored objects.
## Storage
In order to store custom user data in a versioned fashion inside of etcd, we need to also introduce a
`Codec`
-compatible object for persistent storage in etcd. This object is
`ThirdPartyResourceData`
and it contains:
*
Standard API Metadata
*
`Data`
: The raw JSON data for this custom object.
### Storage key specification
Each custom object stored by the API server needs a custom key in storage, this is described below:
#### Definitions
*
`resource-namespace`
: the namespace of the particular resource that is being stored
*
`resource-name`
: the name of the particular resource being stored
*
`third-party-resource-namespace`
: the namespace of the
`ThirdPartyResource`
resource that represents the type for the specific instance being stored.
*
`third-party-resource-name`
: the name of the
`ThirdPartyResource`
resource that represents the type for the specific instance being stored.
#### Key
Given the definitions above, the key for a specific third-party object is:
```
${standard-k8s-prefix}/third-party-resources/${third-party-resource-namespace}/${third-party-resource-name}/${resource-namespace}/${resource-name}
```
Thus, listing a third-party resource can be achieved by listing the directory:
```
${standard-k8s-prefix}/third-party-resources/${third-party-resource-namespace}/${third-party-resource-name}/${resource-namespace}/
```
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[

]()
<!-- END MUNGE: GENERATED_ANALYTICS -->
pkg/api/deep_copy_generated.go
View file @
1055eed9
...
@@ -29,6 +29,12 @@ import (
...
@@ -29,6 +29,12 @@ import (
inf
"speter.net/go/exp/math/dec/inf"
inf
"speter.net/go/exp/math/dec/inf"
)
)
func
deepCopy_api_APIVersion
(
in
APIVersion
,
out
*
APIVersion
,
c
*
conversion
.
Cloner
)
error
{
out
.
Name
=
in
.
Name
out
.
APIGroup
=
in
.
APIGroup
return
nil
}
func
deepCopy_api_AWSElasticBlockStoreVolumeSource
(
in
AWSElasticBlockStoreVolumeSource
,
out
*
AWSElasticBlockStoreVolumeSource
,
c
*
conversion
.
Cloner
)
error
{
func
deepCopy_api_AWSElasticBlockStoreVolumeSource
(
in
AWSElasticBlockStoreVolumeSource
,
out
*
AWSElasticBlockStoreVolumeSource
,
c
*
conversion
.
Cloner
)
error
{
out
.
VolumeID
=
in
.
VolumeID
out
.
VolumeID
=
in
.
VolumeID
out
.
FSType
=
in
.
FSType
out
.
FSType
=
in
.
FSType
...
@@ -2014,6 +2020,65 @@ func deepCopy_api_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *c
...
@@ -2014,6 +2020,65 @@ func deepCopy_api_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *c
return
nil
return
nil
}
}
func
deepCopy_api_ThirdPartyResource
(
in
ThirdPartyResource
,
out
*
ThirdPartyResource
,
c
*
conversion
.
Cloner
)
error
{
if
err
:=
deepCopy_api_TypeMeta
(
in
.
TypeMeta
,
&
out
.
TypeMeta
,
c
);
err
!=
nil
{
return
err
}
if
err
:=
deepCopy_api_ObjectMeta
(
in
.
ObjectMeta
,
&
out
.
ObjectMeta
,
c
);
err
!=
nil
{
return
err
}
out
.
Description
=
in
.
Description
if
in
.
Versions
!=
nil
{
out
.
Versions
=
make
([]
APIVersion
,
len
(
in
.
Versions
))
for
i
:=
range
in
.
Versions
{
if
err
:=
deepCopy_api_APIVersion
(
in
.
Versions
[
i
],
&
out
.
Versions
[
i
],
c
);
err
!=
nil
{
return
err
}
}
}
else
{
out
.
Versions
=
nil
}
return
nil
}
func
deepCopy_api_ThirdPartyResourceData
(
in
ThirdPartyResourceData
,
out
*
ThirdPartyResourceData
,
c
*
conversion
.
Cloner
)
error
{
if
err
:=
deepCopy_api_TypeMeta
(
in
.
TypeMeta
,
&
out
.
TypeMeta
,
c
);
err
!=
nil
{
return
err
}
if
err
:=
deepCopy_api_ObjectMeta
(
in
.
ObjectMeta
,
&
out
.
ObjectMeta
,
c
);
err
!=
nil
{
return
err
}
if
in
.
Data
!=
nil
{
out
.
Data
=
make
([]
uint8
,
len
(
in
.
Data
))
for
i
:=
range
in
.
Data
{
out
.
Data
[
i
]
=
in
.
Data
[
i
]
}
}
else
{
out
.
Data
=
nil
}
return
nil
}
func
deepCopy_api_ThirdPartyResourceList
(
in
ThirdPartyResourceList
,
out
*
ThirdPartyResourceList
,
c
*
conversion
.
Cloner
)
error
{
if
err
:=
deepCopy_api_TypeMeta
(
in
.
TypeMeta
,
&
out
.
TypeMeta
,
c
);
err
!=
nil
{
return
err
}
if
err
:=
deepCopy_api_ListMeta
(
in
.
ListMeta
,
&
out
.
ListMeta
,
c
);
err
!=
nil
{
return
err
}
if
in
.
Items
!=
nil
{
out
.
Items
=
make
([]
ThirdPartyResource
,
len
(
in
.
Items
))
for
i
:=
range
in
.
Items
{
if
err
:=
deepCopy_api_ThirdPartyResource
(
in
.
Items
[
i
],
&
out
.
Items
[
i
],
c
);
err
!=
nil
{
return
err
}
}
}
else
{
out
.
Items
=
nil
}
return
nil
}
func
deepCopy_api_TypeMeta
(
in
TypeMeta
,
out
*
TypeMeta
,
c
*
conversion
.
Cloner
)
error
{
func
deepCopy_api_TypeMeta
(
in
TypeMeta
,
out
*
TypeMeta
,
c
*
conversion
.
Cloner
)
error
{
out
.
Kind
=
in
.
Kind
out
.
Kind
=
in
.
Kind
out
.
APIVersion
=
in
.
APIVersion
out
.
APIVersion
=
in
.
APIVersion
...
@@ -2161,6 +2226,7 @@ func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) erro
...
@@ -2161,6 +2226,7 @@ func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) erro
func
init
()
{
func
init
()
{
err
:=
Scheme
.
AddGeneratedDeepCopyFuncs
(
err
:=
Scheme
.
AddGeneratedDeepCopyFuncs
(
deepCopy_api_APIVersion
,
deepCopy_api_AWSElasticBlockStoreVolumeSource
,
deepCopy_api_AWSElasticBlockStoreVolumeSource
,
deepCopy_api_Binding
,
deepCopy_api_Binding
,
deepCopy_api_Capabilities
,
deepCopy_api_Capabilities
,
...
@@ -2276,6 +2342,9 @@ func init() {
...
@@ -2276,6 +2342,9 @@ func init() {
deepCopy_api_StatusCause
,
deepCopy_api_StatusCause
,
deepCopy_api_StatusDetails
,
deepCopy_api_StatusDetails
,
deepCopy_api_TCPSocketAction
,
deepCopy_api_TCPSocketAction
,
deepCopy_api_ThirdPartyResource
,
deepCopy_api_ThirdPartyResourceData
,
deepCopy_api_ThirdPartyResourceList
,
deepCopy_api_TypeMeta
,
deepCopy_api_TypeMeta
,
deepCopy_api_Volume
,
deepCopy_api_Volume
,
deepCopy_api_VolumeMount
,
deepCopy_api_VolumeMount
,
...
...
pkg/api/latest/latest.go
View file @
1055eed9
...
@@ -95,7 +95,10 @@ func init() {
...
@@ -95,7 +95,10 @@ func init() {
"PodExecOptions"
,
"PodExecOptions"
,
"PodAttachOptions"
,
"PodAttachOptions"
,
"PodProxyOptions"
,
"PodProxyOptions"
,
"Daemon"
)
"Daemon"
,
"ThirdPartyResource"
,
"ThirdPartyResourceData"
,
"ThirdPartyResourceList"
)
mapper
:=
api
.
NewDefaultRESTMapper
(
"api"
,
versions
,
InterfacesFor
,
importPrefix
,
ignoredKinds
,
rootScoped
)
mapper
:=
api
.
NewDefaultRESTMapper
(
"api"
,
versions
,
InterfacesFor
,
importPrefix
,
ignoredKinds
,
rootScoped
)
// setup aliases for groups of resources
// setup aliases for groups of resources
...
...
pkg/api/register.go
View file @
1055eed9
...
@@ -69,6 +69,9 @@ func init() {
...
@@ -69,6 +69,9 @@ func init() {
&
ComponentStatusList
{},
&
ComponentStatusList
{},
&
SerializedReference
{},
&
SerializedReference
{},
&
RangeAllocation
{},
&
RangeAllocation
{},
&
ThirdPartyResource
{},
&
ThirdPartyResourceList
{},
&
ThirdPartyResourceData
{},
)
)
// Legacy names are supported
// Legacy names are supported
Scheme
.
AddKnownTypeWithName
(
""
,
"Minion"
,
&
Node
{})
Scheme
.
AddKnownTypeWithName
(
""
,
"Minion"
,
&
Node
{})
...
@@ -119,3 +122,6 @@ func (*ComponentStatus) IsAnAPIObject() {}
...
@@ -119,3 +122,6 @@ func (*ComponentStatus) IsAnAPIObject() {}
func
(
*
ComponentStatusList
)
IsAnAPIObject
()
{}
func
(
*
ComponentStatusList
)
IsAnAPIObject
()
{}
func
(
*
SerializedReference
)
IsAnAPIObject
()
{}
func
(
*
SerializedReference
)
IsAnAPIObject
()
{}
func
(
*
RangeAllocation
)
IsAnAPIObject
()
{}
func
(
*
RangeAllocation
)
IsAnAPIObject
()
{}
func
(
*
ThirdPartyResource
)
IsAnAPIObject
()
{}
func
(
*
ThirdPartyResourceList
)
IsAnAPIObject
()
{}
func
(
*
ThirdPartyResourceData
)
IsAnAPIObject
()
{}
pkg/api/types.go
View file @
1055eed9
...
@@ -2208,3 +2208,35 @@ type RangeAllocation struct {
...
@@ -2208,3 +2208,35 @@ type RangeAllocation struct {
// a single allocated address (the fifth bit on CIDR 10.0.0.0/8 is 10.0.0.4).
// a single allocated address (the fifth bit on CIDR 10.0.0.0/8 is 10.0.0.4).
Data
[]
byte
`json:"data"`
Data
[]
byte
`json:"data"`
}
}
// A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource
// types to the API. It consists of one or more Versions of the api.
type
ThirdPartyResource
struct
{
TypeMeta
`json:",inline"`
ObjectMeta
`json:"metadata,omitempty" description:"standard object metadata"`
Description
string
`json:"description,omitempty" description:"The description of this object"`
Versions
[]
APIVersion
`json:"versions,omitempty" description:"The versions for this third party object"`
}
type
ThirdPartyResourceList
struct
{
TypeMeta
`json:",inline"`
ListMeta
`json:"metadata,omitempty" description:"standard list metadata; see http://docs.k8s.io/api-conventions.md#metadata"`
Items
[]
ThirdPartyResource
`json:"items" description:"items is a list of schema objects"`
}
// An APIVersion represents a single concrete version of an object model.
type
APIVersion
struct
{
Name
string
`json:"name,omitempty" description:"name of this version (e.g. 'v1')"`
APIGroup
string
`json:"apiGroup,omitempty" description:"The API group to add this object into, default 'experimental'"`
}
// An internal object, used for versioned storage in etcd. Not exposed to the end user.
type
ThirdPartyResourceData
struct
{
TypeMeta
`json:",inline"`
ObjectMeta
`json:"metadata,omitempty" description:"standard object metadata"`
Data
[]
byte
`json:"name,omitempty" description:"the raw JSON data for this data"`
}
pkg/api/v1/conversion_generated.go
View file @
1055eed9
...
@@ -25,6 +25,15 @@ import (
...
@@ -25,6 +25,15 @@ import (
conversion
"k8s.io/kubernetes/pkg/conversion"
conversion
"k8s.io/kubernetes/pkg/conversion"
)
)
func
convert_api_APIVersion_To_v1_APIVersion
(
in
*
api
.
APIVersion
,
out
*
APIVersion
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
api
.
APIVersion
))(
in
)
}
out
.
Name
=
in
.
Name
out
.
APIGroup
=
in
.
APIGroup
return
nil
}
func
convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource
(
in
*
api
.
AWSElasticBlockStoreVolumeSource
,
out
*
AWSElasticBlockStoreVolumeSource
,
s
conversion
.
Scope
)
error
{
func
convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource
(
in
*
api
.
AWSElasticBlockStoreVolumeSource
,
out
*
AWSElasticBlockStoreVolumeSource
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
api
.
AWSElasticBlockStoreVolumeSource
))(
in
)
defaulting
.
(
func
(
*
api
.
AWSElasticBlockStoreVolumeSource
))(
in
)
...
@@ -2243,6 +2252,69 @@ func convert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction,
...
@@ -2243,6 +2252,69 @@ func convert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction,
return
nil
return
nil
}
}
func
convert_api_ThirdPartyResource_To_v1_ThirdPartyResource
(
in
*
api
.
ThirdPartyResource
,
out
*
ThirdPartyResource
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
api
.
ThirdPartyResource
))(
in
)
}
if
err
:=
convert_api_TypeMeta_To_v1_TypeMeta
(
&
in
.
TypeMeta
,
&
out
.
TypeMeta
,
s
);
err
!=
nil
{
return
err
}
if
err
:=
convert_api_ObjectMeta_To_v1_ObjectMeta
(
&
in
.
ObjectMeta
,
&
out
.
ObjectMeta
,
s
);
err
!=
nil
{
return
err
}
out
.
Description
=
in
.
Description
if
in
.
Versions
!=
nil
{
out
.
Versions
=
make
([]
APIVersion
,
len
(
in
.
Versions
))
for
i
:=
range
in
.
Versions
{
if
err
:=
convert_api_APIVersion_To_v1_APIVersion
(
&
in
.
Versions
[
i
],
&
out
.
Versions
[
i
],
s
);
err
!=
nil
{
return
err
}
}
}
else
{
out
.
Versions
=
nil
}
return
nil
}
func
convert_api_ThirdPartyResourceData_To_v1_ThirdPartyResourceData
(
in
*
api
.
ThirdPartyResourceData
,
out
*
ThirdPartyResourceData
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
api
.
ThirdPartyResourceData
))(
in
)
}
if
err
:=
convert_api_TypeMeta_To_v1_TypeMeta
(
&
in
.
TypeMeta
,
&
out
.
TypeMeta
,
s
);
err
!=
nil
{
return
err
}
if
err
:=
convert_api_ObjectMeta_To_v1_ObjectMeta
(
&
in
.
ObjectMeta
,
&
out
.
ObjectMeta
,
s
);
err
!=
nil
{
return
err
}
if
err
:=
s
.
Convert
(
&
in
.
Data
,
&
out
.
Data
,
0
);
err
!=
nil
{
return
err
}
return
nil
}
func
convert_api_ThirdPartyResourceList_To_v1_ThirdPartyResourceList
(
in
*
api
.
ThirdPartyResourceList
,
out
*
ThirdPartyResourceList
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
api
.
ThirdPartyResourceList
))(
in
)
}
if
err
:=
convert_api_TypeMeta_To_v1_TypeMeta
(
&
in
.
TypeMeta
,
&
out
.
TypeMeta
,
s
);
err
!=
nil
{
return
err
}
if
err
:=
convert_api_ListMeta_To_v1_ListMeta
(
&
in
.
ListMeta
,
&
out
.
ListMeta
,
s
);
err
!=
nil
{
return
err
}
if
in
.
Items
!=
nil
{
out
.
Items
=
make
([]
ThirdPartyResource
,
len
(
in
.
Items
))
for
i
:=
range
in
.
Items
{
if
err
:=
convert_api_ThirdPartyResource_To_v1_ThirdPartyResource
(
&
in
.
Items
[
i
],
&
out
.
Items
[
i
],
s
);
err
!=
nil
{
return
err
}
}
}
else
{
out
.
Items
=
nil
}
return
nil
}
func
convert_api_TypeMeta_To_v1_TypeMeta
(
in
*
api
.
TypeMeta
,
out
*
TypeMeta
,
s
conversion
.
Scope
)
error
{
func
convert_api_TypeMeta_To_v1_TypeMeta
(
in
*
api
.
TypeMeta
,
out
*
TypeMeta
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
api
.
TypeMeta
))(
in
)
defaulting
.
(
func
(
*
api
.
TypeMeta
))(
in
)
...
@@ -2368,6 +2440,15 @@ func convert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *Volu
...
@@ -2368,6 +2440,15 @@ func convert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *Volu
return
nil
return
nil
}
}
func
convert_v1_APIVersion_To_api_APIVersion
(
in
*
APIVersion
,
out
*
api
.
APIVersion
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
APIVersion
))(
in
)
}
out
.
Name
=
in
.
Name
out
.
APIGroup
=
in
.
APIGroup
return
nil
}
func
convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource
(
in
*
AWSElasticBlockStoreVolumeSource
,
out
*
api
.
AWSElasticBlockStoreVolumeSource
,
s
conversion
.
Scope
)
error
{
func
convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource
(
in
*
AWSElasticBlockStoreVolumeSource
,
out
*
api
.
AWSElasticBlockStoreVolumeSource
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
AWSElasticBlockStoreVolumeSource
))(
in
)
defaulting
.
(
func
(
*
AWSElasticBlockStoreVolumeSource
))(
in
)
...
@@ -4586,6 +4667,69 @@ func convert_v1_TCPSocketAction_To_api_TCPSocketAction(in *TCPSocketAction, out
...
@@ -4586,6 +4667,69 @@ func convert_v1_TCPSocketAction_To_api_TCPSocketAction(in *TCPSocketAction, out
return
nil
return
nil
}
}
func
convert_v1_ThirdPartyResource_To_api_ThirdPartyResource
(
in
*
ThirdPartyResource
,
out
*
api
.
ThirdPartyResource
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
ThirdPartyResource
))(
in
)
}
if
err
:=
convert_v1_TypeMeta_To_api_TypeMeta
(
&
in
.
TypeMeta
,
&
out
.
TypeMeta
,
s
);
err
!=
nil
{
return
err
}
if
err
:=
convert_v1_ObjectMeta_To_api_ObjectMeta
(
&
in
.
ObjectMeta
,
&
out
.
ObjectMeta
,
s
);
err
!=
nil
{
return
err
}
out
.
Description
=
in
.
Description
if
in
.
Versions
!=
nil
{
out
.
Versions
=
make
([]
api
.
APIVersion
,
len
(
in
.
Versions
))
for
i
:=
range
in
.
Versions
{
if
err
:=
convert_v1_APIVersion_To_api_APIVersion
(
&
in
.
Versions
[
i
],
&
out
.
Versions
[
i
],
s
);
err
!=
nil
{
return
err
}
}
}
else
{
out
.
Versions
=
nil
}
return
nil
}
func
convert_v1_ThirdPartyResourceData_To_api_ThirdPartyResourceData
(
in
*
ThirdPartyResourceData
,
out
*
api
.
ThirdPartyResourceData
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
ThirdPartyResourceData
))(
in
)
}
if
err
:=
convert_v1_TypeMeta_To_api_TypeMeta
(
&
in
.
TypeMeta
,
&
out
.
TypeMeta
,
s
);
err
!=
nil
{
return
err
}
if
err
:=
convert_v1_ObjectMeta_To_api_ObjectMeta
(
&
in
.
ObjectMeta
,
&
out
.
ObjectMeta
,
s
);
err
!=
nil
{
return
err
}
if
err
:=
s
.
Convert
(
&
in
.
Data
,
&
out
.
Data
,
0
);
err
!=
nil
{
return
err
}
return
nil
}
func
convert_v1_ThirdPartyResourceList_To_api_ThirdPartyResourceList
(
in
*
ThirdPartyResourceList
,
out
*
api
.
ThirdPartyResourceList
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
ThirdPartyResourceList
))(
in
)
}
if
err
:=
convert_v1_TypeMeta_To_api_TypeMeta
(
&
in
.
TypeMeta
,
&
out
.
TypeMeta
,
s
);
err
!=
nil
{
return
err
}
if
err
:=
convert_v1_ListMeta_To_api_ListMeta
(
&
in
.
ListMeta
,
&
out
.
ListMeta
,
s
);
err
!=
nil
{
return
err
}
if
in
.
Items
!=
nil
{
out
.
Items
=
make
([]
api
.
ThirdPartyResource
,
len
(
in
.
Items
))
for
i
:=
range
in
.
Items
{
if
err
:=
convert_v1_ThirdPartyResource_To_api_ThirdPartyResource
(
&
in
.
Items
[
i
],
&
out
.
Items
[
i
],
s
);
err
!=
nil
{
return
err
}
}
}
else
{
out
.
Items
=
nil
}
return
nil
}
func
convert_v1_TypeMeta_To_api_TypeMeta
(
in
*
TypeMeta
,
out
*
api
.
TypeMeta
,
s
conversion
.
Scope
)
error
{
func
convert_v1_TypeMeta_To_api_TypeMeta
(
in
*
TypeMeta
,
out
*
api
.
TypeMeta
,
s
conversion
.
Scope
)
error
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
if
defaulting
,
found
:=
s
.
DefaultingInterface
(
reflect
.
TypeOf
(
*
in
));
found
{
defaulting
.
(
func
(
*
TypeMeta
))(
in
)
defaulting
.
(
func
(
*
TypeMeta
))(
in
)
...
@@ -4713,6 +4857,7 @@ func convert_v1_VolumeSource_To_api_VolumeSource(in *VolumeSource, out *api.Volu
...
@@ -4713,6 +4857,7 @@ func convert_v1_VolumeSource_To_api_VolumeSource(in *VolumeSource, out *api.Volu
func
init
()
{
func
init
()
{
err
:=
api
.
Scheme
.
AddGeneratedConversionFuncs
(
err
:=
api
.
Scheme
.
AddGeneratedConversionFuncs
(
convert_api_APIVersion_To_v1_APIVersion
,
convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource
,
convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource
,
convert_api_Binding_To_v1_Binding
,
convert_api_Binding_To_v1_Binding
,
convert_api_Capabilities_To_v1_Capabilities
,
convert_api_Capabilities_To_v1_Capabilities
,
...
@@ -4826,10 +4971,14 @@ func init() {
...
@@ -4826,10 +4971,14 @@ func init() {
convert_api_StatusDetails_To_v1_StatusDetails
,
convert_api_StatusDetails_To_v1_StatusDetails
,
convert_api_Status_To_v1_Status
,
convert_api_Status_To_v1_Status
,
convert_api_TCPSocketAction_To_v1_TCPSocketAction
,
convert_api_TCPSocketAction_To_v1_TCPSocketAction
,
convert_api_ThirdPartyResourceData_To_v1_ThirdPartyResourceData
,
convert_api_ThirdPartyResourceList_To_v1_ThirdPartyResourceList
,
convert_api_ThirdPartyResource_To_v1_ThirdPartyResource
,
convert_api_TypeMeta_To_v1_TypeMeta
,
convert_api_TypeMeta_To_v1_TypeMeta
,
convert_api_VolumeMount_To_v1_VolumeMount
,
convert_api_VolumeMount_To_v1_VolumeMount
,
convert_api_VolumeSource_To_v1_VolumeSource
,
convert_api_VolumeSource_To_v1_VolumeSource
,
convert_api_Volume_To_v1_Volume
,
convert_api_Volume_To_v1_Volume
,
convert_v1_APIVersion_To_api_APIVersion
,
convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource
,
convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource
,
convert_v1_Binding_To_api_Binding
,
convert_v1_Binding_To_api_Binding
,
convert_v1_Capabilities_To_api_Capabilities
,
convert_v1_Capabilities_To_api_Capabilities
,
...
@@ -4943,6 +5092,9 @@ func init() {
...
@@ -4943,6 +5092,9 @@ func init() {
convert_v1_StatusDetails_To_api_StatusDetails
,
convert_v1_StatusDetails_To_api_StatusDetails
,
convert_v1_Status_To_api_Status
,
convert_v1_Status_To_api_Status
,
convert_v1_TCPSocketAction_To_api_TCPSocketAction
,
convert_v1_TCPSocketAction_To_api_TCPSocketAction
,
convert_v1_ThirdPartyResourceData_To_api_ThirdPartyResourceData
,
convert_v1_ThirdPartyResourceList_To_api_ThirdPartyResourceList
,
convert_v1_ThirdPartyResource_To_api_ThirdPartyResource
,
convert_v1_TypeMeta_To_api_TypeMeta
,
convert_v1_TypeMeta_To_api_TypeMeta
,
convert_v1_VolumeMount_To_api_VolumeMount
,
convert_v1_VolumeMount_To_api_VolumeMount
,
convert_v1_VolumeSource_To_api_VolumeSource
,
convert_v1_VolumeSource_To_api_VolumeSource
,
...
...
pkg/api/v1/deep_copy_generated.go
View file @
1055eed9
...
@@ -44,6 +44,12 @@ func deepCopy_resource_Quantity(in resource.Quantity, out *resource.Quantity, c
...
@@ -44,6 +44,12 @@ func deepCopy_resource_Quantity(in resource.Quantity, out *resource.Quantity, c
return
nil
return
nil
}
}
func
deepCopy_v1_APIVersion
(
in
APIVersion
,
out
*
APIVersion
,
c
*
conversion
.
Cloner
)
error
{
out
.
Name
=
in
.
Name
out
.
APIGroup
=
in
.
APIGroup
return
nil
}
func
deepCopy_v1_AWSElasticBlockStoreVolumeSource
(
in
AWSElasticBlockStoreVolumeSource
,
out
*
AWSElasticBlockStoreVolumeSource
,
c
*
conversion
.
Cloner
)
error
{
func
deepCopy_v1_AWSElasticBlockStoreVolumeSource
(
in
AWSElasticBlockStoreVolumeSource
,
out
*
AWSElasticBlockStoreVolumeSource
,
c
*
conversion
.
Cloner
)
error
{
out
.
VolumeID
=
in
.
VolumeID
out
.
VolumeID
=
in
.
VolumeID
out
.
FSType
=
in
.
FSType
out
.
FSType
=
in
.
FSType
...
@@ -2019,6 +2025,65 @@ func deepCopy_v1_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *co
...
@@ -2019,6 +2025,65 @@ func deepCopy_v1_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *co
return
nil
return
nil
}
}
func
deepCopy_v1_ThirdPartyResource
(
in
ThirdPartyResource
,
out
*
ThirdPartyResource
,
c
*
conversion
.
Cloner
)
error
{
if
err
:=
deepCopy_v1_TypeMeta
(
in
.
TypeMeta
,
&
out
.
TypeMeta
,
c
);
err
!=
nil
{
return
err
}
if
err
:=
deepCopy_v1_ObjectMeta
(
in
.
ObjectMeta
,
&
out
.
ObjectMeta
,
c
);
err
!=
nil
{
return
err
}
out
.
Description
=
in
.
Description
if
in
.
Versions
!=
nil
{
out
.
Versions
=
make
([]
APIVersion
,
len
(
in
.
Versions
))
for
i
:=
range
in
.
Versions
{
if
err
:=
deepCopy_v1_APIVersion
(
in
.
Versions
[
i
],
&
out
.
Versions
[
i
],
c
);
err
!=
nil
{
return
err
}
}
}
else
{
out
.
Versions
=
nil
}
return
nil
}
func
deepCopy_v1_ThirdPartyResourceData
(
in
ThirdPartyResourceData
,
out
*
ThirdPartyResourceData
,
c
*
conversion
.
Cloner
)
error
{
if
err
:=
deepCopy_v1_TypeMeta
(
in
.
TypeMeta
,
&
out
.
TypeMeta
,
c
);
err
!=
nil
{
return
err
}
if
err
:=
deepCopy_v1_ObjectMeta
(
in
.
ObjectMeta
,
&
out
.
ObjectMeta
,
c
);
err
!=
nil
{
return
err
}
if
in
.
Data
!=
nil
{
out
.
Data
=
make
([]
uint8
,
len
(
in
.
Data
))
for
i
:=
range
in
.
Data
{
out
.
Data
[
i
]
=
in
.
Data
[
i
]
}
}
else
{
out
.
Data
=
nil
}
return
nil
}
func
deepCopy_v1_ThirdPartyResourceList
(
in
ThirdPartyResourceList
,
out
*
ThirdPartyResourceList
,
c
*
conversion
.
Cloner
)
error
{
if
err
:=
deepCopy_v1_TypeMeta
(
in
.
TypeMeta
,
&
out
.
TypeMeta
,
c
);
err
!=
nil
{
return
err
}
if
err
:=
deepCopy_v1_ListMeta
(
in
.
ListMeta
,
&
out
.
ListMeta
,
c
);
err
!=
nil
{
return
err
}
if
in
.
Items
!=
nil
{
out
.
Items
=
make
([]
ThirdPartyResource
,
len
(
in
.
Items
))
for
i
:=
range
in
.
Items
{
if
err
:=
deepCopy_v1_ThirdPartyResource
(
in
.
Items
[
i
],
&
out
.
Items
[
i
],
c
);
err
!=
nil
{
return
err
}
}
}
else
{
out
.
Items
=
nil
}
return
nil
}
func
deepCopy_v1_TypeMeta
(
in
TypeMeta
,
out
*
TypeMeta
,
c
*
conversion
.
Cloner
)
error
{
func
deepCopy_v1_TypeMeta
(
in
TypeMeta
,
out
*
TypeMeta
,
c
*
conversion
.
Cloner
)
error
{
out
.
Kind
=
in
.
Kind
out
.
Kind
=
in
.
Kind
out
.
APIVersion
=
in
.
APIVersion
out
.
APIVersion
=
in
.
APIVersion
...
@@ -2163,6 +2228,7 @@ func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) erro
...
@@ -2163,6 +2228,7 @@ func deepCopy_util_Time(in util.Time, out *util.Time, c *conversion.Cloner) erro
func
init
()
{
func
init
()
{
err
:=
api
.
Scheme
.
AddGeneratedDeepCopyFuncs
(
err
:=
api
.
Scheme
.
AddGeneratedDeepCopyFuncs
(
deepCopy_resource_Quantity
,
deepCopy_resource_Quantity
,
deepCopy_v1_APIVersion
,
deepCopy_v1_AWSElasticBlockStoreVolumeSource
,
deepCopy_v1_AWSElasticBlockStoreVolumeSource
,
deepCopy_v1_Binding
,
deepCopy_v1_Binding
,
deepCopy_v1_Capabilities
,
deepCopy_v1_Capabilities
,
...
@@ -2278,6 +2344,9 @@ func init() {
...
@@ -2278,6 +2344,9 @@ func init() {
deepCopy_v1_StatusCause
,
deepCopy_v1_StatusCause
,
deepCopy_v1_StatusDetails
,
deepCopy_v1_StatusDetails
,
deepCopy_v1_TCPSocketAction
,
deepCopy_v1_TCPSocketAction
,
deepCopy_v1_ThirdPartyResource
,
deepCopy_v1_ThirdPartyResourceData
,
deepCopy_v1_ThirdPartyResourceList
,
deepCopy_v1_TypeMeta
,
deepCopy_v1_TypeMeta
,
deepCopy_v1_Volume
,
deepCopy_v1_Volume
,
deepCopy_v1_VolumeMount
,
deepCopy_v1_VolumeMount
,
...
...
pkg/api/v1/register.go
View file @
1055eed9
...
@@ -84,6 +84,9 @@ func addKnownTypes() {
...
@@ -84,6 +84,9 @@ func addKnownTypes() {
&
ComponentStatusList
{},
&
ComponentStatusList
{},
&
SerializedReference
{},
&
SerializedReference
{},
&
RangeAllocation
{},
&
RangeAllocation
{},
&
ThirdPartyResource
{},
&
ThirdPartyResourceList
{},
&
ThirdPartyResourceData
{},
)
)
// Legacy names are supported
// Legacy names are supported
api
.
Scheme
.
AddKnownTypeWithName
(
"v1"
,
"Minion"
,
&
Node
{})
api
.
Scheme
.
AddKnownTypeWithName
(
"v1"
,
"Minion"
,
&
Node
{})
...
@@ -134,3 +137,6 @@ func (*ComponentStatus) IsAnAPIObject() {}
...
@@ -134,3 +137,6 @@ func (*ComponentStatus) IsAnAPIObject() {}
func
(
*
ComponentStatusList
)
IsAnAPIObject
()
{}
func
(
*
ComponentStatusList
)
IsAnAPIObject
()
{}
func
(
*
SerializedReference
)
IsAnAPIObject
()
{}
func
(
*
SerializedReference
)
IsAnAPIObject
()
{}
func
(
*
RangeAllocation
)
IsAnAPIObject
()
{}
func
(
*
RangeAllocation
)
IsAnAPIObject
()
{}
func
(
*
ThirdPartyResource
)
IsAnAPIObject
()
{}
func
(
*
ThirdPartyResourceList
)
IsAnAPIObject
()
{}
func
(
*
ThirdPartyResourceData
)
IsAnAPIObject
()
{}
pkg/api/v1/types.go
View file @
1055eed9
...
@@ -2060,3 +2060,35 @@ type RangeAllocation struct {
...
@@ -2060,3 +2060,35 @@ type RangeAllocation struct {
Range
string
`json:"range" description:"a range string that identifies the range represented by 'data'; required"`
Range
string
`json:"range" description:"a range string that identifies the range represented by 'data'; required"`
Data
[]
byte
`json:"data" description:"a bit array containing all allocated addresses in the previous segment"`
Data
[]
byte
`json:"data" description:"a bit array containing all allocated addresses in the previous segment"`
}
}
// A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource
// types to the API. It consists of one or more Versions of the api.
type
ThirdPartyResource
struct
{
TypeMeta
`json:",inline"`
ObjectMeta
`json:"metadata,omitempty" description:"standard object metadata"`
Description
string
`json:"description,omitempty" description:"The description of this object"`
Versions
[]
APIVersion
`json:"versions,omitempty" description:"The versions for this third party object"`
}
type
ThirdPartyResourceList
struct
{
TypeMeta
`json:",inline"`
ListMeta
`json:"metadata,omitempty" description:"standard list metadata; see http://docs.k8s.io/api-conventions.md#metadata"`
Items
[]
ThirdPartyResource
`json:"items" description:"items is a list of schema objects"`
}
// An APIVersion represents a single concrete version of an object model.
type
APIVersion
struct
{
Name
string
`json:"name,omitempty" description:"name of this version (e.g. 'v1')"`
APIGroup
string
`json:"apiGroup,omitempty" description:"The API group to add this object into, default 'experimental'"`
}
// An internal object, used for versioned storage in etcd. Not exposed to the end user.
type
ThirdPartyResourceData
struct
{
TypeMeta
`json:",inline"`
ObjectMeta
`json:"metadata,omitempty" description:"standard object metadata"`
Data
[]
byte
`json:"name,omitempty" description:"the raw JSON data for this data"`
}
pkg/api/validation/validation.go
View file @
1055eed9
...
@@ -1802,3 +1802,26 @@ func ValidateSecurityContext(sc *api.SecurityContext) errs.ValidationErrorList {
...
@@ -1802,3 +1802,26 @@ func ValidateSecurityContext(sc *api.SecurityContext) errs.ValidationErrorList {
}
}
return
allErrs
return
allErrs
}
}
func
ValidateThirdPartyResource
(
obj
*
api
.
ThirdPartyResource
)
errs
.
ValidationErrorList
{
allErrs
:=
errs
.
ValidationErrorList
{}
if
len
(
obj
.
Name
)
==
0
{
allErrs
=
append
(
allErrs
,
errs
.
NewFieldInvalid
(
"name"
,
obj
.
Name
,
"name must be non-empty"
))
}
versions
:=
util
.
StringSet
{}
for
ix
:=
range
obj
.
Versions
{
version
:=
&
obj
.
Versions
[
ix
]
if
len
(
version
.
Name
)
==
0
{
allErrs
=
append
(
allErrs
,
errs
.
NewFieldInvalid
(
"name"
,
version
,
"name can not be empty"
))
}
if
versions
.
Has
(
version
.
Name
)
{
allErrs
=
append
(
allErrs
,
errs
.
NewFieldDuplicate
(
"version"
,
version
))
}
versions
.
Insert
(
version
.
Name
)
}
return
allErrs
}
func
ValidateSchemaUpdate
(
oldResource
,
newResource
*
api
.
ThirdPartyResource
)
errs
.
ValidationErrorList
{
return
errs
.
ValidationErrorList
{
fmt
.
Errorf
(
"Schema update is not supported."
)}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment