mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
[ui] Complete container creation (#7035)
* API returns more info about container * Display more info about containers * Update UI static files * Fix unit tests * Get/Set sources configuration * [ui] create container with sources mount configuration * e2e tests + ui static files * Set containers's envvars * Regenerate UI static files * Add Annotation to POST /container * [api] Create Container with Annotations * [ui] Annotations when creating container * Regenerate UI static files * [api] Endpoints when adding container * [ui] Endpoints when adding container * Regenerate UI static files
This commit is contained in:
@@ -512,6 +512,9 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- image
|
||||
properties:
|
||||
name:
|
||||
description: Name of the container
|
||||
@@ -531,6 +534,11 @@ paths:
|
||||
items: {
|
||||
type: string
|
||||
}
|
||||
env:
|
||||
description: Environment variables to define
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Env'
|
||||
memReq:
|
||||
description: Requested memory for the deployed container
|
||||
type: string
|
||||
@@ -548,6 +556,24 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VolumeMount'
|
||||
configureSources:
|
||||
description: If false, mountSources and sourceMapping values are not considered
|
||||
type: boolean
|
||||
mountSources:
|
||||
description: If true, sources are mounted into container's filesystem
|
||||
type: boolean
|
||||
sourceMapping:
|
||||
description: Specific directory on which to mount sources
|
||||
type: string
|
||||
annotation:
|
||||
description: Annotations added to the resources created for this container
|
||||
$ref: '#/components/schemas/Annotation'
|
||||
endpoints:
|
||||
description: Endpoints exposed by the container
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Endpoint'
|
||||
|
||||
responses:
|
||||
'200':
|
||||
description: container was successfully added to the devfile
|
||||
@@ -1522,6 +1548,12 @@ components:
|
||||
- cpuRequest
|
||||
- cpuLimit
|
||||
- volumeMounts
|
||||
- annotation
|
||||
- endpoints
|
||||
- env
|
||||
- configureSources
|
||||
- mountSources
|
||||
- sourceMapping
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
@@ -1547,6 +1579,22 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VolumeMount'
|
||||
annotation:
|
||||
$ref: '#/components/schemas/Annotation'
|
||||
endpoints:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Endpoint'
|
||||
env:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Env'
|
||||
configureSources:
|
||||
type: boolean
|
||||
mountSources:
|
||||
type: boolean
|
||||
sourceMapping:
|
||||
type: string
|
||||
VolumeMount:
|
||||
type: object
|
||||
required:
|
||||
@@ -1557,6 +1605,50 @@ components:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
Annotation:
|
||||
type: object
|
||||
required:
|
||||
- deployment
|
||||
- service
|
||||
properties:
|
||||
deployment:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
service:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
Endpoint:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- targetPort
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
exposure:
|
||||
type: string
|
||||
enum: [public,internal,none]
|
||||
path:
|
||||
type: string
|
||||
protocol:
|
||||
type: string
|
||||
enum: [http,https,ws,wss,tcp,udp]
|
||||
secure:
|
||||
type: boolean
|
||||
targetPort:
|
||||
type: integer
|
||||
Env:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
Image:
|
||||
type: object
|
||||
required:
|
||||
|
||||
3
pkg/apiserver-gen/.openapi-generator/FILES
generated
3
pkg/apiserver-gen/.openapi-generator/FILES
generated
@@ -22,6 +22,7 @@ go/model__devstate_quantity_valid_post_request.go
|
||||
go/model__devstate_resource_post_request.go
|
||||
go/model__devstate_volume_post_request.go
|
||||
go/model__instance_get_200_response.go
|
||||
go/model_annotation.go
|
||||
go/model_apply_command.go
|
||||
go/model_command.go
|
||||
go/model_composite_command.go
|
||||
@@ -29,6 +30,8 @@ go/model_container.go
|
||||
go/model_devfile_content.go
|
||||
go/model_devfile_put_request.go
|
||||
go/model_devstate_devfile_put_request.go
|
||||
go/model_endpoint.go
|
||||
go/model_env.go
|
||||
go/model_events.go
|
||||
go/model_exec_command.go
|
||||
go/model_general_error.go
|
||||
|
||||
@@ -12,10 +12,10 @@ package openapi
|
||||
type DevstateContainerPostRequest struct {
|
||||
|
||||
// Name of the container
|
||||
Name string `json:"name,omitempty"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// Container image
|
||||
Image string `json:"image,omitempty"`
|
||||
Image string `json:"image"`
|
||||
|
||||
// Entrypoint of the container
|
||||
Command []string `json:"command,omitempty"`
|
||||
@@ -23,6 +23,9 @@ type DevstateContainerPostRequest struct {
|
||||
// Args passed to the Container entrypoint
|
||||
Args []string `json:"args,omitempty"`
|
||||
|
||||
// Environment variables to define
|
||||
Env []Env `json:"env,omitempty"`
|
||||
|
||||
// Requested memory for the deployed container
|
||||
MemReq string `json:"memReq,omitempty"`
|
||||
|
||||
@@ -37,15 +40,52 @@ type DevstateContainerPostRequest struct {
|
||||
|
||||
// Volume to mount into the container filesystem
|
||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"`
|
||||
|
||||
// If false, mountSources and sourceMapping values are not considered
|
||||
ConfigureSources bool `json:"configureSources,omitempty"`
|
||||
|
||||
// If true, sources are mounted into container's filesystem
|
||||
MountSources bool `json:"mountSources,omitempty"`
|
||||
|
||||
// Specific directory on which to mount sources
|
||||
SourceMapping string `json:"sourceMapping,omitempty"`
|
||||
|
||||
Annotation Annotation `json:"annotation,omitempty"`
|
||||
|
||||
// Endpoints exposed by the container
|
||||
Endpoints []Endpoint `json:"endpoints,omitempty"`
|
||||
}
|
||||
|
||||
// AssertDevstateContainerPostRequestRequired checks if the required fields are not zero-ed
|
||||
func AssertDevstateContainerPostRequestRequired(obj DevstateContainerPostRequest) error {
|
||||
elements := map[string]interface{}{
|
||||
"name": obj.Name,
|
||||
"image": obj.Image,
|
||||
}
|
||||
for name, el := range elements {
|
||||
if isZero := IsZeroValue(el); isZero {
|
||||
return &RequiredError{Field: name}
|
||||
}
|
||||
}
|
||||
|
||||
for _, el := range obj.Env {
|
||||
if err := AssertEnvRequired(el); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, el := range obj.VolumeMounts {
|
||||
if err := AssertVolumeMountRequired(el); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := AssertAnnotationRequired(obj.Annotation); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, el := range obj.Endpoints {
|
||||
if err := AssertEndpointRequired(el); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
43
pkg/apiserver-gen/go/model_annotation.go
generated
Normal file
43
pkg/apiserver-gen/go/model_annotation.go
generated
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
type Annotation struct {
|
||||
Deployment map[string]string `json:"deployment"`
|
||||
|
||||
Service map[string]string `json:"service"`
|
||||
}
|
||||
|
||||
// AssertAnnotationRequired checks if the required fields are not zero-ed
|
||||
func AssertAnnotationRequired(obj Annotation) error {
|
||||
elements := map[string]interface{}{
|
||||
"deployment": obj.Deployment,
|
||||
"service": obj.Service,
|
||||
}
|
||||
for name, el := range elements {
|
||||
if isZero := IsZeroValue(el); isZero {
|
||||
return &RequiredError{Field: name}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertRecurseAnnotationRequired recursively checks if required fields are not zero-ed in a nested slice.
|
||||
// Accepts only nested slice of Annotation (e.g. [][]Annotation), otherwise ErrTypeAssertionError is thrown.
|
||||
func AssertRecurseAnnotationRequired(objSlice interface{}) error {
|
||||
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
|
||||
aAnnotation, ok := obj.(Annotation)
|
||||
if !ok {
|
||||
return ErrTypeAssertionError
|
||||
}
|
||||
return AssertAnnotationRequired(aAnnotation)
|
||||
})
|
||||
}
|
||||
49
pkg/apiserver-gen/go/model_container.go
generated
49
pkg/apiserver-gen/go/model_container.go
generated
@@ -27,20 +27,38 @@ type Container struct {
|
||||
CpuLimit string `json:"cpuLimit"`
|
||||
|
||||
VolumeMounts []VolumeMount `json:"volumeMounts"`
|
||||
|
||||
Annotation Annotation `json:"annotation"`
|
||||
|
||||
Endpoints []Endpoint `json:"endpoints"`
|
||||
|
||||
Env []Env `json:"env"`
|
||||
|
||||
ConfigureSources bool `json:"configureSources"`
|
||||
|
||||
MountSources bool `json:"mountSources"`
|
||||
|
||||
SourceMapping string `json:"sourceMapping"`
|
||||
}
|
||||
|
||||
// AssertContainerRequired checks if the required fields are not zero-ed
|
||||
func AssertContainerRequired(obj Container) error {
|
||||
elements := map[string]interface{}{
|
||||
"name": obj.Name,
|
||||
"image": obj.Image,
|
||||
"command": obj.Command,
|
||||
"args": obj.Args,
|
||||
"memoryRequest": obj.MemoryRequest,
|
||||
"memoryLimit": obj.MemoryLimit,
|
||||
"cpuRequest": obj.CpuRequest,
|
||||
"cpuLimit": obj.CpuLimit,
|
||||
"volumeMounts": obj.VolumeMounts,
|
||||
"name": obj.Name,
|
||||
"image": obj.Image,
|
||||
"command": obj.Command,
|
||||
"args": obj.Args,
|
||||
"memoryRequest": obj.MemoryRequest,
|
||||
"memoryLimit": obj.MemoryLimit,
|
||||
"cpuRequest": obj.CpuRequest,
|
||||
"cpuLimit": obj.CpuLimit,
|
||||
"volumeMounts": obj.VolumeMounts,
|
||||
"annotation": obj.Annotation,
|
||||
"endpoints": obj.Endpoints,
|
||||
"env": obj.Env,
|
||||
"configureSources": obj.ConfigureSources,
|
||||
"mountSources": obj.MountSources,
|
||||
"sourceMapping": obj.SourceMapping,
|
||||
}
|
||||
for name, el := range elements {
|
||||
if isZero := IsZeroValue(el); isZero {
|
||||
@@ -53,6 +71,19 @@ func AssertContainerRequired(obj Container) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := AssertAnnotationRequired(obj.Annotation); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, el := range obj.Endpoints {
|
||||
if err := AssertEndpointRequired(el); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, el := range obj.Env {
|
||||
if err := AssertEnvRequired(el); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
51
pkg/apiserver-gen/go/model_endpoint.go
generated
Normal file
51
pkg/apiserver-gen/go/model_endpoint.go
generated
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
type Endpoint struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
Exposure string `json:"exposure,omitempty"`
|
||||
|
||||
Path string `json:"path,omitempty"`
|
||||
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
|
||||
Secure bool `json:"secure,omitempty"`
|
||||
|
||||
TargetPort int32 `json:"targetPort"`
|
||||
}
|
||||
|
||||
// AssertEndpointRequired checks if the required fields are not zero-ed
|
||||
func AssertEndpointRequired(obj Endpoint) error {
|
||||
elements := map[string]interface{}{
|
||||
"name": obj.Name,
|
||||
"targetPort": obj.TargetPort,
|
||||
}
|
||||
for name, el := range elements {
|
||||
if isZero := IsZeroValue(el); isZero {
|
||||
return &RequiredError{Field: name}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertRecurseEndpointRequired recursively checks if required fields are not zero-ed in a nested slice.
|
||||
// Accepts only nested slice of Endpoint (e.g. [][]Endpoint), otherwise ErrTypeAssertionError is thrown.
|
||||
func AssertRecurseEndpointRequired(objSlice interface{}) error {
|
||||
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
|
||||
aEndpoint, ok := obj.(Endpoint)
|
||||
if !ok {
|
||||
return ErrTypeAssertionError
|
||||
}
|
||||
return AssertEndpointRequired(aEndpoint)
|
||||
})
|
||||
}
|
||||
43
pkg/apiserver-gen/go/model_env.go
generated
Normal file
43
pkg/apiserver-gen/go/model_env.go
generated
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
type Env struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// AssertEnvRequired checks if the required fields are not zero-ed
|
||||
func AssertEnvRequired(obj Env) error {
|
||||
elements := map[string]interface{}{
|
||||
"name": obj.Name,
|
||||
"value": obj.Value,
|
||||
}
|
||||
for name, el := range elements {
|
||||
if isZero := IsZeroValue(el); isZero {
|
||||
return &RequiredError{Field: name}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertRecurseEnvRequired recursively checks if required fields are not zero-ed in a nested slice.
|
||||
// Accepts only nested slice of Env (e.g. [][]Env), otherwise ErrTypeAssertionError is thrown.
|
||||
func AssertRecurseEnvRequired(objSlice interface{}) error {
|
||||
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
|
||||
aEnv, ok := obj.(Env)
|
||||
if !ok {
|
||||
return ErrTypeAssertionError
|
||||
}
|
||||
return AssertEnvRequired(aEnv)
|
||||
})
|
||||
}
|
||||
@@ -15,11 +15,17 @@ func (s *DevstateApiService) DevstateContainerPost(ctx context.Context, containe
|
||||
container.Image,
|
||||
container.Command,
|
||||
container.Args,
|
||||
container.Env,
|
||||
container.MemReq,
|
||||
container.MemLimit,
|
||||
container.CpuReq,
|
||||
container.CpuLimit,
|
||||
container.VolumeMounts,
|
||||
container.ConfigureSources,
|
||||
container.MountSources,
|
||||
container.SourceMapping,
|
||||
container.Annotation,
|
||||
container.Endpoints,
|
||||
)
|
||||
if err != nil {
|
||||
return openapi.Response(http.StatusInternalServerError, openapi.GeneralError{
|
||||
|
||||
@@ -33,11 +33,17 @@ func TestDevfileState_AddExecCommand(t *testing.T) {
|
||||
"an-image",
|
||||
[]string{"run", "command"},
|
||||
[]string{"arg1", "arg2"},
|
||||
nil,
|
||||
"1Gi",
|
||||
"2Gi",
|
||||
"100m",
|
||||
"200m",
|
||||
nil,
|
||||
true,
|
||||
true,
|
||||
"",
|
||||
openapi.Annotation{},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -72,6 +78,7 @@ components:
|
||||
image: an-image
|
||||
memoryLimit: 2Gi
|
||||
memoryRequest: 1Gi
|
||||
mountSources: true
|
||||
name: a-container
|
||||
metadata: {}
|
||||
schemaVersion: 2.2.0
|
||||
@@ -90,15 +97,19 @@ schemaVersion: 2.2.0
|
||||
},
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "a-container",
|
||||
Image: "an-image",
|
||||
Command: []string{"run", "command"},
|
||||
Args: []string{"arg1", "arg2"},
|
||||
MemoryRequest: "1Gi",
|
||||
MemoryLimit: "2Gi",
|
||||
CpuRequest: "100m",
|
||||
CpuLimit: "200m",
|
||||
VolumeMounts: []openapi.VolumeMount{},
|
||||
Name: "a-container",
|
||||
Image: "an-image",
|
||||
Command: []string{"run", "command"},
|
||||
Args: []string{"arg1", "arg2"},
|
||||
MemoryRequest: "1Gi",
|
||||
MemoryLimit: "2Gi",
|
||||
CpuRequest: "100m",
|
||||
CpuLimit: "200m",
|
||||
VolumeMounts: []openapi.VolumeMount{},
|
||||
Endpoints: []openapi.Endpoint{},
|
||||
Env: []openapi.Env{},
|
||||
ConfigureSources: true,
|
||||
MountSources: true,
|
||||
},
|
||||
},
|
||||
Images: []Image{},
|
||||
@@ -236,11 +247,17 @@ func TestDevfileState_AddCompositeCommand(t *testing.T) {
|
||||
"an-image",
|
||||
[]string{"run", "command"},
|
||||
[]string{"arg1", "arg2"},
|
||||
nil,
|
||||
"1Gi",
|
||||
"2Gi",
|
||||
"100m",
|
||||
"200m",
|
||||
nil,
|
||||
true,
|
||||
true,
|
||||
"",
|
||||
openapi.Annotation{},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -288,6 +305,7 @@ components:
|
||||
image: an-image
|
||||
memoryLimit: 2Gi
|
||||
memoryRequest: 1Gi
|
||||
mountSources: true
|
||||
name: a-container
|
||||
metadata: {}
|
||||
schemaVersion: 2.2.0
|
||||
@@ -311,15 +329,19 @@ schemaVersion: 2.2.0
|
||||
},
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "a-container",
|
||||
Image: "an-image",
|
||||
Command: []string{"run", "command"},
|
||||
Args: []string{"arg1", "arg2"},
|
||||
MemoryRequest: "1Gi",
|
||||
MemoryLimit: "2Gi",
|
||||
CpuRequest: "100m",
|
||||
CpuLimit: "200m",
|
||||
VolumeMounts: []openapi.VolumeMount{},
|
||||
Name: "a-container",
|
||||
Image: "an-image",
|
||||
Command: []string{"run", "command"},
|
||||
Args: []string{"arg1", "arg2"},
|
||||
MemoryRequest: "1Gi",
|
||||
MemoryLimit: "2Gi",
|
||||
CpuRequest: "100m",
|
||||
CpuLimit: "200m",
|
||||
VolumeMounts: []openapi.VolumeMount{},
|
||||
Endpoints: []openapi.Endpoint{},
|
||||
Env: []openapi.Env{},
|
||||
ConfigureSources: true,
|
||||
MountSources: true,
|
||||
},
|
||||
},
|
||||
Images: []Image{},
|
||||
@@ -368,11 +390,17 @@ func TestDevfileState_DeleteCommand(t *testing.T) {
|
||||
"an-image",
|
||||
[]string{"run", "command"},
|
||||
[]string{"arg1", "arg2"},
|
||||
nil,
|
||||
"1Gi",
|
||||
"2Gi",
|
||||
"100m",
|
||||
"200m",
|
||||
nil,
|
||||
true,
|
||||
true,
|
||||
"",
|
||||
openapi.Annotation{},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -406,6 +434,7 @@ func TestDevfileState_DeleteCommand(t *testing.T) {
|
||||
image: an-image
|
||||
memoryLimit: 2Gi
|
||||
memoryRequest: 1Gi
|
||||
mountSources: true
|
||||
name: a-container
|
||||
metadata: {}
|
||||
schemaVersion: 2.2.0
|
||||
@@ -413,15 +442,19 @@ schemaVersion: 2.2.0
|
||||
Commands: []Command{},
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "a-container",
|
||||
Image: "an-image",
|
||||
Command: []string{"run", "command"},
|
||||
Args: []string{"arg1", "arg2"},
|
||||
MemoryRequest: "1Gi",
|
||||
MemoryLimit: "2Gi",
|
||||
CpuRequest: "100m",
|
||||
CpuLimit: "200m",
|
||||
VolumeMounts: []openapi.VolumeMount{},
|
||||
Name: "a-container",
|
||||
Image: "an-image",
|
||||
Command: []string{"run", "command"},
|
||||
Args: []string{"arg1", "arg2"},
|
||||
MemoryRequest: "1Gi",
|
||||
MemoryLimit: "2Gi",
|
||||
CpuRequest: "100m",
|
||||
CpuLimit: "200m",
|
||||
VolumeMounts: []openapi.VolumeMount{},
|
||||
Endpoints: []openapi.Endpoint{},
|
||||
Env: []openapi.Env{},
|
||||
ConfigureSources: true,
|
||||
MountSources: true,
|
||||
},
|
||||
},
|
||||
Images: []Image{},
|
||||
|
||||
@@ -14,11 +14,17 @@ func (o *DevfileState) AddContainer(
|
||||
image string,
|
||||
command []string,
|
||||
args []string,
|
||||
envs []Env,
|
||||
memRequest string,
|
||||
memLimit string,
|
||||
cpuRequest string,
|
||||
cpuLimit string,
|
||||
volumeMounts []VolumeMount,
|
||||
configureSources bool,
|
||||
mountSources bool,
|
||||
sourceMapping string,
|
||||
annotation Annotation,
|
||||
endpoints []Endpoint,
|
||||
) (DevfileContent, error) {
|
||||
v1alpha2VolumeMounts := make([]v1alpha2.VolumeMount, 0, len(volumeMounts))
|
||||
for _, vm := range volumeMounts {
|
||||
@@ -27,6 +33,38 @@ func (o *DevfileState) AddContainer(
|
||||
Path: vm.Path,
|
||||
})
|
||||
}
|
||||
|
||||
v1alpha2Envs := make([]v1alpha2.EnvVar, 0, len(envs))
|
||||
for _, env := range envs {
|
||||
v1alpha2Envs = append(v1alpha2Envs, v1alpha2.EnvVar{
|
||||
Name: env.Name,
|
||||
Value: env.Value,
|
||||
})
|
||||
}
|
||||
var annotations *v1alpha2.Annotation
|
||||
if len(annotation.Deployment) > 0 || len(annotation.Service) > 0 {
|
||||
annotations = &v1alpha2.Annotation{}
|
||||
if len(annotation.Deployment) > 0 {
|
||||
annotations.Deployment = annotation.Deployment
|
||||
}
|
||||
if len(annotation.Service) > 0 {
|
||||
annotations.Service = annotation.Service
|
||||
}
|
||||
}
|
||||
|
||||
v1alpha2Endpoints := make([]v1alpha2.Endpoint, 0, len(endpoints))
|
||||
for _, endpoint := range endpoints {
|
||||
endpoint := endpoint
|
||||
v1alpha2Endpoints = append(v1alpha2Endpoints, v1alpha2.Endpoint{
|
||||
Name: endpoint.Name,
|
||||
TargetPort: int(endpoint.TargetPort),
|
||||
Exposure: v1alpha2.EndpointExposure(endpoint.Exposure),
|
||||
Protocol: v1alpha2.EndpointProtocol(endpoint.Protocol),
|
||||
Secure: &endpoint.Secure,
|
||||
Path: endpoint.Path,
|
||||
})
|
||||
}
|
||||
|
||||
container := v1alpha2.Component{
|
||||
Name: name,
|
||||
ComponentUnion: v1alpha2.ComponentUnion{
|
||||
@@ -35,15 +73,22 @@ func (o *DevfileState) AddContainer(
|
||||
Image: image,
|
||||
Command: command,
|
||||
Args: args,
|
||||
Env: v1alpha2Envs,
|
||||
MemoryRequest: memRequest,
|
||||
MemoryLimit: memLimit,
|
||||
CpuRequest: cpuRequest,
|
||||
CpuLimit: cpuLimit,
|
||||
VolumeMounts: v1alpha2VolumeMounts,
|
||||
Annotation: annotations,
|
||||
},
|
||||
Endpoints: v1alpha2Endpoints,
|
||||
},
|
||||
},
|
||||
}
|
||||
if configureSources {
|
||||
container.Container.MountSources = &mountSources
|
||||
container.Container.SourceMapping = sourceMapping
|
||||
}
|
||||
err := o.Devfile.Data.AddComponents([]v1alpha2.Component{container})
|
||||
if err != nil {
|
||||
return DevfileContent{}, err
|
||||
|
||||
@@ -10,15 +10,21 @@ import (
|
||||
|
||||
func TestDevfileState_AddContainer(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
image string
|
||||
command []string
|
||||
args []string
|
||||
memRequest string
|
||||
memLimit string
|
||||
cpuRequest string
|
||||
cpuLimit string
|
||||
volumeMounts []openapi.VolumeMount
|
||||
name string
|
||||
image string
|
||||
command []string
|
||||
args []string
|
||||
envs []Env
|
||||
memRequest string
|
||||
memLimit string
|
||||
cpuRequest string
|
||||
cpuLimit string
|
||||
volumeMounts []openapi.VolumeMount
|
||||
configureSources bool
|
||||
mountSources bool
|
||||
sourceMapping string
|
||||
annotation Annotation
|
||||
endpoints []Endpoint
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -28,7 +34,7 @@ func TestDevfileState_AddContainer(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Add a container",
|
||||
name: "Add a container, with sources configured",
|
||||
state: func() DevfileState {
|
||||
return NewDevfileState()
|
||||
},
|
||||
@@ -47,6 +53,81 @@ func TestDevfileState_AddContainer(t *testing.T) {
|
||||
Path: "/mnt/volume1",
|
||||
},
|
||||
},
|
||||
configureSources: true,
|
||||
mountSources: false,
|
||||
},
|
||||
want: DevfileContent{
|
||||
Content: `components:
|
||||
- container:
|
||||
args:
|
||||
- arg1
|
||||
- arg2
|
||||
command:
|
||||
- run
|
||||
- command
|
||||
cpuLimit: 200m
|
||||
cpuRequest: 100m
|
||||
image: an-image
|
||||
memoryLimit: 2Gi
|
||||
memoryRequest: 1Gi
|
||||
mountSources: false
|
||||
volumeMounts:
|
||||
- name: vol1
|
||||
path: /mnt/volume1
|
||||
name: a-name
|
||||
metadata: {}
|
||||
schemaVersion: 2.2.0
|
||||
`,
|
||||
Commands: []Command{},
|
||||
Containers: []Container{
|
||||
{
|
||||
Name: "a-name",
|
||||
Image: "an-image",
|
||||
Command: []string{"run", "command"},
|
||||
Args: []string{"arg1", "arg2"},
|
||||
MemoryRequest: "1Gi",
|
||||
MemoryLimit: "2Gi",
|
||||
CpuRequest: "100m",
|
||||
CpuLimit: "200m",
|
||||
VolumeMounts: []openapi.VolumeMount{
|
||||
{
|
||||
Name: "vol1",
|
||||
Path: "/mnt/volume1",
|
||||
},
|
||||
},
|
||||
Endpoints: []openapi.Endpoint{},
|
||||
Env: []openapi.Env{},
|
||||
ConfigureSources: true,
|
||||
MountSources: false,
|
||||
},
|
||||
},
|
||||
Images: []Image{},
|
||||
Resources: []Resource{},
|
||||
Volumes: []Volume{},
|
||||
Events: Events{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Add a container, without sources configured",
|
||||
state: func() DevfileState {
|
||||
return NewDevfileState()
|
||||
},
|
||||
args: args{
|
||||
name: "a-name",
|
||||
image: "an-image",
|
||||
command: []string{"run", "command"},
|
||||
args: []string{"arg1", "arg2"},
|
||||
memRequest: "1Gi",
|
||||
memLimit: "2Gi",
|
||||
cpuRequest: "100m",
|
||||
cpuLimit: "200m",
|
||||
volumeMounts: []openapi.VolumeMount{
|
||||
{
|
||||
Name: "vol1",
|
||||
Path: "/mnt/volume1",
|
||||
},
|
||||
},
|
||||
configureSources: false,
|
||||
},
|
||||
want: DevfileContent{
|
||||
Content: `components:
|
||||
@@ -86,6 +167,10 @@ schemaVersion: 2.2.0
|
||||
Path: "/mnt/volume1",
|
||||
},
|
||||
},
|
||||
Endpoints: []openapi.Endpoint{},
|
||||
Env: []openapi.Env{},
|
||||
ConfigureSources: false,
|
||||
MountSources: true,
|
||||
},
|
||||
},
|
||||
Images: []Image{},
|
||||
@@ -99,7 +184,7 @@ schemaVersion: 2.2.0
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := tt.state()
|
||||
got, err := o.AddContainer(tt.args.name, tt.args.image, tt.args.command, tt.args.args, tt.args.memRequest, tt.args.memLimit, tt.args.cpuRequest, tt.args.cpuLimit, tt.args.volumeMounts)
|
||||
got, err := o.AddContainer(tt.args.name, tt.args.image, tt.args.command, tt.args.args, tt.args.envs, tt.args.memRequest, tt.args.memLimit, tt.args.cpuRequest, tt.args.cpuLimit, tt.args.volumeMounts, tt.args.configureSources, tt.args.mountSources, tt.args.sourceMapping, tt.args.annotation, tt.args.endpoints)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DevfileState.AddContainer() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
@@ -134,11 +219,17 @@ func TestDevfileState_DeleteContainer(t *testing.T) {
|
||||
"an-image",
|
||||
[]string{"run", "command"},
|
||||
[]string{"arg1", "arg2"},
|
||||
nil,
|
||||
"1Gi",
|
||||
"2Gi",
|
||||
"100m",
|
||||
"200m",
|
||||
nil,
|
||||
true,
|
||||
false,
|
||||
"",
|
||||
Annotation{},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -169,11 +260,17 @@ schemaVersion: 2.2.0
|
||||
"an-image",
|
||||
[]string{"run", "command"},
|
||||
[]string{"arg1", "arg2"},
|
||||
nil,
|
||||
"1Gi",
|
||||
"2Gi",
|
||||
"100m",
|
||||
"200m",
|
||||
nil,
|
||||
true,
|
||||
false,
|
||||
"",
|
||||
Annotation{},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -162,15 +162,21 @@ func (o *DevfileState) getContainers() ([]Container, error) {
|
||||
result := make([]Container, 0, len(containers))
|
||||
for _, container := range containers {
|
||||
result = append(result, Container{
|
||||
Name: container.Name,
|
||||
Image: container.ComponentUnion.Container.Image,
|
||||
Command: container.ComponentUnion.Container.Command,
|
||||
Args: container.ComponentUnion.Container.Args,
|
||||
MemoryRequest: container.ComponentUnion.Container.MemoryRequest,
|
||||
MemoryLimit: container.ComponentUnion.Container.MemoryLimit,
|
||||
CpuRequest: container.ComponentUnion.Container.CpuRequest,
|
||||
CpuLimit: container.ComponentUnion.Container.CpuLimit,
|
||||
VolumeMounts: o.getVolumeMounts(container.Container.Container),
|
||||
Name: container.Name,
|
||||
Image: container.ComponentUnion.Container.Image,
|
||||
Command: container.ComponentUnion.Container.Command,
|
||||
Args: container.ComponentUnion.Container.Args,
|
||||
MemoryRequest: container.ComponentUnion.Container.MemoryRequest,
|
||||
MemoryLimit: container.ComponentUnion.Container.MemoryLimit,
|
||||
CpuRequest: container.ComponentUnion.Container.CpuRequest,
|
||||
CpuLimit: container.ComponentUnion.Container.CpuLimit,
|
||||
VolumeMounts: o.getVolumeMounts(container.Container.Container),
|
||||
Annotation: o.getAnnotation(container.Container.Annotation),
|
||||
Endpoints: o.getEndpoints(container.Container.Endpoints),
|
||||
Env: o.getEnv(container.Container.Env),
|
||||
ConfigureSources: container.Container.MountSources != nil,
|
||||
MountSources: pointer.BoolDeref(container.Container.MountSources, true), // TODO(feloy) default value will depend on dedicatedPod
|
||||
SourceMapping: container.Container.SourceMapping,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
@@ -187,6 +193,42 @@ func (o *DevfileState) getVolumeMounts(container v1alpha2.Container) []VolumeMou
|
||||
return result
|
||||
}
|
||||
|
||||
func (o *DevfileState) getAnnotation(annotation *v1alpha2.Annotation) Annotation {
|
||||
if annotation == nil {
|
||||
return Annotation{}
|
||||
}
|
||||
return Annotation{
|
||||
Deployment: annotation.Deployment,
|
||||
Service: annotation.Service,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *DevfileState) getEndpoints(endpoints []v1alpha2.Endpoint) []Endpoint {
|
||||
result := make([]Endpoint, 0, len(endpoints))
|
||||
for _, ep := range endpoints {
|
||||
result = append(result, Endpoint{
|
||||
Name: ep.Name,
|
||||
Exposure: string(ep.Exposure),
|
||||
Path: ep.Path,
|
||||
Protocol: string(ep.Protocol),
|
||||
Secure: pointer.BoolDeref(ep.Secure, false),
|
||||
TargetPort: int32(ep.TargetPort),
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (o *DevfileState) getEnv(envs []v1alpha2.EnvVar) []Env {
|
||||
result := make([]Env, 0, len(envs))
|
||||
for _, env := range envs {
|
||||
result = append(result, Env{
|
||||
Name: env.Name,
|
||||
Value: env.Value,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (o *DevfileState) getImages() ([]Image, error) {
|
||||
images, err := o.Devfile.Data.GetComponents(common.DevfileOptions{
|
||||
ComponentOptions: common.ComponentOptions{
|
||||
|
||||
@@ -512,6 +512,9 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- image
|
||||
properties:
|
||||
name:
|
||||
description: Name of the container
|
||||
@@ -531,6 +534,11 @@ paths:
|
||||
items: {
|
||||
type: string
|
||||
}
|
||||
env:
|
||||
description: Environment variables to define
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Env'
|
||||
memReq:
|
||||
description: Requested memory for the deployed container
|
||||
type: string
|
||||
@@ -548,6 +556,24 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VolumeMount'
|
||||
configureSources:
|
||||
description: If false, mountSources and sourceMapping values are not considered
|
||||
type: boolean
|
||||
mountSources:
|
||||
description: If true, sources are mounted into container's filesystem
|
||||
type: boolean
|
||||
sourceMapping:
|
||||
description: Specific directory on which to mount sources
|
||||
type: string
|
||||
annotation:
|
||||
description: Annotations added to the resources created for this container
|
||||
$ref: '#/components/schemas/Annotation'
|
||||
endpoints:
|
||||
description: Endpoints exposed by the container
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Endpoint'
|
||||
|
||||
responses:
|
||||
'200':
|
||||
description: container was successfully added to the devfile
|
||||
@@ -1522,6 +1548,12 @@ components:
|
||||
- cpuRequest
|
||||
- cpuLimit
|
||||
- volumeMounts
|
||||
- annotation
|
||||
- endpoints
|
||||
- env
|
||||
- configureSources
|
||||
- mountSources
|
||||
- sourceMapping
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
@@ -1547,6 +1579,22 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VolumeMount'
|
||||
annotation:
|
||||
$ref: '#/components/schemas/Annotation'
|
||||
endpoints:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Endpoint'
|
||||
env:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Env'
|
||||
configureSources:
|
||||
type: boolean
|
||||
mountSources:
|
||||
type: boolean
|
||||
sourceMapping:
|
||||
type: string
|
||||
VolumeMount:
|
||||
type: object
|
||||
required:
|
||||
@@ -1557,6 +1605,50 @@ components:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
Annotation:
|
||||
type: object
|
||||
required:
|
||||
- deployment
|
||||
- service
|
||||
properties:
|
||||
deployment:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
service:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
Endpoint:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- targetPort
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
exposure:
|
||||
type: string
|
||||
enum: [public,internal,none]
|
||||
path:
|
||||
type: string
|
||||
protocol:
|
||||
type: string
|
||||
enum: [http,https,ws,wss,tcp,udp]
|
||||
secure:
|
||||
type: boolean
|
||||
targetPort:
|
||||
type: integer
|
||||
Env:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
Image:
|
||||
type: object
|
||||
required:
|
||||
|
||||
4
pkg/apiserver-impl/ui/index.html
generated
4
pkg/apiserver-impl/ui/index.html
generated
File diff suppressed because one or more lines are too long
1
pkg/apiserver-impl/ui/main.1046d99cec4375b1.js
generated
1
pkg/apiserver-impl/ui/main.1046d99cec4375b1.js
generated
File diff suppressed because one or more lines are too long
1
pkg/apiserver-impl/ui/main.fcdb01b1f229861f.js
generated
Normal file
1
pkg/apiserver-impl/ui/main.fcdb01b1f229861f.js
generated
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -39,7 +39,7 @@ describe('devfile editor spec', () => {
|
||||
.should('contain.text', 'with arg');
|
||||
});
|
||||
|
||||
it('displays a created container', () => {
|
||||
it('displays a created container without source configuration', () => {
|
||||
cy.init();
|
||||
|
||||
cy.selectTab(TAB_VOLUMES);
|
||||
@@ -51,6 +51,87 @@ describe('devfile editor spec', () => {
|
||||
cy.selectTab(TAB_CONTAINERS);
|
||||
cy.getByDataCy('container-name').type('created-container');
|
||||
cy.getByDataCy('container-image').type('an-image');
|
||||
cy.getByDataCy('container-env-add').click();
|
||||
cy.getByDataCy('container-env-name-0').type("VAR1");
|
||||
cy.getByDataCy('container-env-value-0').type("val1");
|
||||
cy.getByDataCy('container-env-plus').click();
|
||||
cy.getByDataCy('container-env-name-1').type("VAR2");
|
||||
cy.getByDataCy('container-env-value-1').type("val2");
|
||||
cy.getByDataCy('container-env-plus').click();
|
||||
cy.getByDataCy('container-env-name-2').type("VAR3");
|
||||
cy.getByDataCy('container-env-value-2').type("val3");
|
||||
|
||||
cy.getByDataCy('volume-mount-add').click();
|
||||
cy.getByDataCy('volume-mount-path-0').type("/mnt/vol1");
|
||||
cy.getByDataCy('volume-mount-name-0').click().get('mat-option').contains('volume1').click();
|
||||
|
||||
cy.getByDataCy('endpoints-add').click();
|
||||
cy.getByDataCy('endpoint-name-0').type("ep1");
|
||||
cy.getByDataCy('endpoint-targetPort-0').type("4001");
|
||||
|
||||
cy.getByDataCy('volume-mount-add').click();
|
||||
cy.getByDataCy('volume-mount-path-1').type("/mnt/vol2");
|
||||
cy.getByDataCy('volume-mount-name-1').click().get('mat-option').contains('(New Volume)').click();
|
||||
cy.getByDataCy('volume-name').type('volume2');
|
||||
cy.getByDataCy('volume-create').click();
|
||||
|
||||
cy.getByDataCy('container-more-params').click();
|
||||
cy.getByDataCy('container-deploy-anno-add').click();
|
||||
cy.getByDataCy('container-deploy-anno-name-0').type("DEPANNO1");
|
||||
cy.getByDataCy('container-deploy-anno-value-0').type("depval1");
|
||||
cy.getByDataCy('container-deploy-anno-plus').click();
|
||||
cy.getByDataCy('container-deploy-anno-name-1').type("DEPANNO2");
|
||||
cy.getByDataCy('container-deploy-anno-value-1').type("depval2");
|
||||
cy.getByDataCy('container-svc-anno-add').click();
|
||||
cy.getByDataCy('container-svc-anno-name-0').type("SVCANNO1");
|
||||
cy.getByDataCy('container-svc-anno-value-0').type("svcval1");
|
||||
cy.getByDataCy('container-svc-anno-plus').click();
|
||||
cy.getByDataCy('container-svc-anno-name-1').type("SVCANNO2");
|
||||
cy.getByDataCy('container-svc-anno-value-1').type("svcval2");
|
||||
|
||||
cy.getByDataCy('container-create').click();
|
||||
|
||||
cy.getByDataCy('container-info').first()
|
||||
.should('contain.text', 'created-container')
|
||||
.should('contain.text', 'an-image')
|
||||
.should('contain.text', 'VAR1: val1')
|
||||
.should('contain.text', 'VAR2: val2')
|
||||
.should('contain.text', 'VAR3: val3')
|
||||
.should('contain.text', 'volume1')
|
||||
.should('contain.text', '/mnt/vol1')
|
||||
.should('contain.text', 'volume2')
|
||||
.should('contain.text', '/mnt/vol2')
|
||||
.should('not.contain.text', 'Mount Sources')
|
||||
.should('contain.text', 'ep1')
|
||||
.should('contain.text', '4001')
|
||||
.should('contain.text', 'Deployment Annotations')
|
||||
.should('contain.text', 'DEPANNO1: depval1')
|
||||
.should('contain.text', 'DEPANNO2: depval2')
|
||||
.should('contain.text', 'Service Annotations')
|
||||
.should('contain.text', 'SVCANNO1: svcval1')
|
||||
.should('contain.text', 'SVCANNO2: svcval2');
|
||||
|
||||
cy.selectTab(TAB_VOLUMES);
|
||||
cy.getByDataCy('volume-info').eq(1)
|
||||
.should('contain.text', 'volume2');
|
||||
});
|
||||
|
||||
it('displays a created container with source configuration', () => {
|
||||
cy.init();
|
||||
|
||||
cy.selectTab(TAB_VOLUMES);
|
||||
cy.getByDataCy('volume-name').type('volume1');
|
||||
cy.getByDataCy('volume-size').type('512Mi');
|
||||
cy.getByDataCy('volume-ephemeral').click();
|
||||
cy.getByDataCy('volume-create').click();
|
||||
|
||||
cy.selectTab(TAB_CONTAINERS);
|
||||
cy.getByDataCy('container-name').type('created-container');
|
||||
cy.getByDataCy('container-image').type('an-image');
|
||||
cy.getByDataCy('container-more-params').click();
|
||||
cy.getByDataCy('container-sources-configuration').click();
|
||||
cy.getByDataCy('container-sources-specific-directory').click();
|
||||
cy.getByDataCy('container-source-mapping').type('/mnt/sources');
|
||||
|
||||
cy.getByDataCy('volume-mount-add').click();
|
||||
cy.getByDataCy('volume-mount-path-0').type("/mnt/vol1");
|
||||
@@ -70,7 +151,9 @@ describe('devfile editor spec', () => {
|
||||
.should('contain.text', 'volume1')
|
||||
.should('contain.text', '/mnt/vol1')
|
||||
.should('contain.text', 'volume2')
|
||||
.should('contain.text', '/mnt/vol2');
|
||||
.should('contain.text', '/mnt/vol2')
|
||||
.should('contain.text', 'Mount Sources')
|
||||
.should('contain.text', '/mnt/sources');
|
||||
|
||||
cy.selectTab(TAB_VOLUMES);
|
||||
cy.getByDataCy('volume-info').eq(1)
|
||||
|
||||
3
ui/src/app/api-gen/.openapi-generator/FILES
generated
3
ui/src/app/api-gen/.openapi-generator/FILES
generated
@@ -8,6 +8,7 @@ configuration.ts
|
||||
encoder.ts
|
||||
git_push.sh
|
||||
index.ts
|
||||
model/annotation.ts
|
||||
model/applyCommand.ts
|
||||
model/command.ts
|
||||
model/componentCommandPostRequest.ts
|
||||
@@ -30,6 +31,8 @@ model/devstateImagePostRequest.ts
|
||||
model/devstateQuantityValidPostRequest.ts
|
||||
model/devstateResourcePostRequest.ts
|
||||
model/devstateVolumePostRequest.ts
|
||||
model/endpoint.ts
|
||||
model/env.ts
|
||||
model/events.ts
|
||||
model/execCommand.ts
|
||||
model/generalError.ts
|
||||
|
||||
18
ui/src/app/api-gen/model/annotation.ts
generated
Normal file
18
ui/src/app/api-gen/model/annotation.ts
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* odo dev
|
||||
* API interface for \'odo dev\'
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
export interface Annotation {
|
||||
deployment: { [key: string]: string; };
|
||||
service: { [key: string]: string; };
|
||||
}
|
||||
|
||||
9
ui/src/app/api-gen/model/container.ts
generated
9
ui/src/app/api-gen/model/container.ts
generated
@@ -9,7 +9,10 @@
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
import { Endpoint } from './endpoint';
|
||||
import { VolumeMount } from './volumeMount';
|
||||
import { Env } from './env';
|
||||
import { Annotation } from './annotation';
|
||||
|
||||
|
||||
export interface Container {
|
||||
@@ -22,5 +25,11 @@ export interface Container {
|
||||
cpuRequest: string;
|
||||
cpuLimit: string;
|
||||
volumeMounts: Array<VolumeMount>;
|
||||
annotation: Annotation;
|
||||
endpoints: Array<Endpoint>;
|
||||
env: Array<Env>;
|
||||
configureSources: boolean;
|
||||
mountSources: boolean;
|
||||
sourceMapping: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,18 +9,21 @@
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
import { Endpoint } from './endpoint';
|
||||
import { VolumeMount } from './volumeMount';
|
||||
import { Env } from './env';
|
||||
import { Annotation } from './annotation';
|
||||
|
||||
|
||||
export interface DevstateContainerPostRequest {
|
||||
/**
|
||||
* Name of the container
|
||||
*/
|
||||
name?: string;
|
||||
name: string;
|
||||
/**
|
||||
* Container image
|
||||
*/
|
||||
image?: string;
|
||||
image: string;
|
||||
/**
|
||||
* Entrypoint of the container
|
||||
*/
|
||||
@@ -29,6 +32,10 @@ export interface DevstateContainerPostRequest {
|
||||
* Args passed to the Container entrypoint
|
||||
*/
|
||||
args?: Array<string>;
|
||||
/**
|
||||
* Environment variables to define
|
||||
*/
|
||||
env?: Array<Env>;
|
||||
/**
|
||||
* Requested memory for the deployed container
|
||||
*/
|
||||
@@ -49,5 +56,22 @@ export interface DevstateContainerPostRequest {
|
||||
* Volume to mount into the container filesystem
|
||||
*/
|
||||
volumeMounts?: Array<VolumeMount>;
|
||||
/**
|
||||
* If false, mountSources and sourceMapping values are not considered
|
||||
*/
|
||||
configureSources?: boolean;
|
||||
/**
|
||||
* If true, sources are mounted into container\'s filesystem
|
||||
*/
|
||||
mountSources?: boolean;
|
||||
/**
|
||||
* Specific directory on which to mount sources
|
||||
*/
|
||||
sourceMapping?: string;
|
||||
annotation?: Annotation;
|
||||
/**
|
||||
* Endpoints exposed by the container
|
||||
*/
|
||||
endpoints?: Array<Endpoint>;
|
||||
}
|
||||
|
||||
|
||||
40
ui/src/app/api-gen/model/endpoint.ts
generated
Normal file
40
ui/src/app/api-gen/model/endpoint.ts
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* odo dev
|
||||
* API interface for \'odo dev\'
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
export interface Endpoint {
|
||||
name: string;
|
||||
exposure?: Endpoint.ExposureEnum;
|
||||
path?: string;
|
||||
protocol?: Endpoint.ProtocolEnum;
|
||||
secure?: boolean;
|
||||
targetPort: number;
|
||||
}
|
||||
export namespace Endpoint {
|
||||
export type ExposureEnum = 'public' | 'internal' | 'none';
|
||||
export const ExposureEnum = {
|
||||
Public: 'public' as ExposureEnum,
|
||||
Internal: 'internal' as ExposureEnum,
|
||||
None: 'none' as ExposureEnum
|
||||
};
|
||||
export type ProtocolEnum = 'http' | 'https' | 'ws' | 'wss' | 'tcp' | 'udp';
|
||||
export const ProtocolEnum = {
|
||||
Http: 'http' as ProtocolEnum,
|
||||
Https: 'https' as ProtocolEnum,
|
||||
Ws: 'ws' as ProtocolEnum,
|
||||
Wss: 'wss' as ProtocolEnum,
|
||||
Tcp: 'tcp' as ProtocolEnum,
|
||||
Udp: 'udp' as ProtocolEnum
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
18
ui/src/app/api-gen/model/env.ts
generated
Normal file
18
ui/src/app/api-gen/model/env.ts
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* odo dev
|
||||
* API interface for \'odo dev\'
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
export interface Env {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
3
ui/src/app/api-gen/model/models.ts
generated
3
ui/src/app/api-gen/model/models.ts
generated
@@ -1,3 +1,4 @@
|
||||
export * from './annotation';
|
||||
export * from './applyCommand';
|
||||
export * from './command';
|
||||
export * from './componentCommandPostRequest';
|
||||
@@ -20,6 +21,8 @@ export * from './devstateImagePostRequest';
|
||||
export * from './devstateQuantityValidPostRequest';
|
||||
export * from './devstateResourcePostRequest';
|
||||
export * from './devstateVolumePostRequest';
|
||||
export * from './endpoint';
|
||||
export * from './env';
|
||||
export * from './events';
|
||||
export * from './execCommand';
|
||||
export * from './generalError';
|
||||
|
||||
@@ -48,6 +48,8 @@ import { ConfirmComponent } from './components/confirm/confirm.component';
|
||||
import { VolumesComponent } from './tabs/volumes/volumes.component';
|
||||
import { VolumeComponent } from './forms/volume/volume.component';
|
||||
import { VolumeMountsComponent } from './controls/volume-mounts/volume-mounts.component';
|
||||
import { MultiKeyValueComponent } from './controls/multi-key-value/multi-key-value.component';
|
||||
import { EndpointsComponent } from './controls/endpoints/endpoints.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -74,6 +76,8 @@ import { VolumeMountsComponent } from './controls/volume-mounts/volume-mounts.co
|
||||
VolumesComponent,
|
||||
VolumeComponent,
|
||||
VolumeMountsComponent,
|
||||
MultiKeyValueComponent,
|
||||
EndpointsComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
2
ui/src/app/controls/endpoints/endpoints.component.css
Normal file
2
ui/src/app/controls/endpoints/endpoints.component.css
Normal file
@@ -0,0 +1,2 @@
|
||||
.mid-width { width: 50%; }
|
||||
.quart-width { width: 25%; }
|
||||
43
ui/src/app/controls/endpoints/endpoints.component.html
Normal file
43
ui/src/app/controls/endpoints/endpoints.component.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<div *ngFor="let control of form.controls; index as i">
|
||||
<ng-container [formGroup]="control">
|
||||
<mat-form-field class="mid-width" appearance="outline">
|
||||
<mat-label><span>Name</span></mat-label>
|
||||
<input [attr.data-cy]="'endpoint-name-'+i" matInput formControlName="name">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="quart-width" appearance="outline">
|
||||
<mat-label><span>Target Port</span></mat-label>
|
||||
<input [attr.data-cy]="'endpoint-targetPort-'+i" type="number" matInput formControlName="targetPort">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="quart-width" appearance="outline">
|
||||
<mat-label>Exposure</mat-label>
|
||||
<mat-select [attr.data-cy]="'endpoint-exposure-'+i" formControlName="exposure">
|
||||
<mat-option value="">(default, public)</mat-option>
|
||||
<mat-option value="public">public</mat-option>
|
||||
<mat-option value="internal">internal</mat-option>
|
||||
<mat-option value="none">none</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="mid-width" appearance="outline">
|
||||
<mat-label><span>Path</span></mat-label>
|
||||
<input [attr.data-cy]="'endpoint-path-'+i" matInput formControlName="path">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="quart-width" appearance="outline">
|
||||
<mat-label>Protocol</mat-label>
|
||||
<mat-select [attr.data-cy]="'endpoint-protocol-'+i" formControlName="protocol">
|
||||
<mat-option value="">(default, http)</mat-option>
|
||||
<mat-option value="http">http</mat-option>
|
||||
<mat-option value="https">https</mat-option>
|
||||
<mat-option value="ws">ws</mat-option>
|
||||
<mat-option value="wss">wss</mat-option>
|
||||
<mat-option value="tcp">tcp</mat-option>
|
||||
<mat-option value="udp">udp</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-checkbox [attr.data-cy]="'endpoint-secure-'+i" formControlName="secure">Protocol Is Secure</mat-checkbox>
|
||||
</ng-container>
|
||||
</div>
|
||||
<button data-cy="endpoints-plus" *ngIf="form.value.length > 0" mat-icon-button (click)="addEndpoint()">
|
||||
<mat-icon class="tab-icon material-icons-outlined">add</mat-icon>
|
||||
</button>
|
||||
<button data-cy="endpoints-add" *ngIf="form.value.length == 0" mat-flat-button (click)="addEndpoint()">Add an Endpoint</button>
|
||||
23
ui/src/app/controls/endpoints/endpoints.component.spec.ts
Normal file
23
ui/src/app/controls/endpoints/endpoints.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EndpointsComponent } from './endpoints.component';
|
||||
|
||||
describe('EndpointsComponent', () => {
|
||||
let component: EndpointsComponent;
|
||||
let fixture: ComponentFixture<EndpointsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ EndpointsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EndpointsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
74
ui/src/app/controls/endpoints/endpoints.component.ts
Normal file
74
ui/src/app/controls/endpoints/endpoints.component.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Component, forwardRef } from '@angular/core';
|
||||
import { AbstractControl, ControlValueAccessor, FormArray, FormControl, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator, Validators } from '@angular/forms';
|
||||
|
||||
interface Endpoint {
|
||||
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-endpoints',
|
||||
templateUrl: './endpoints.component.html',
|
||||
styleUrls: ['./endpoints.component.css'],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
multi: true,
|
||||
useExisting: EndpointsComponent
|
||||
},
|
||||
{
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => EndpointsComponent),
|
||||
multi: true,
|
||||
},
|
||||
]
|
||||
})
|
||||
export class EndpointsComponent implements ControlValueAccessor, Validator {
|
||||
|
||||
onChange = (_: Endpoint[]) => {};
|
||||
onValidatorChange = () => {};
|
||||
|
||||
form = new FormArray<FormGroup>([]);
|
||||
|
||||
constructor() {
|
||||
this.form.valueChanges.subscribe(value => {
|
||||
this.onChange(value);
|
||||
});
|
||||
}
|
||||
|
||||
newEndpoint(): FormGroup {
|
||||
return new FormGroup({
|
||||
name: new FormControl("", [Validators.required]),
|
||||
targetPort: new FormControl("", [Validators.required, Validators.pattern("^[0-9]*$")]),
|
||||
exposure: new FormControl(""),
|
||||
path: new FormControl(""),
|
||||
protocol: new FormControl(""),
|
||||
secure: new FormControl(false),
|
||||
});
|
||||
}
|
||||
|
||||
addEndpoint() {
|
||||
this.form.push(this.newEndpoint());
|
||||
}
|
||||
|
||||
/* ControlValueAccessor implementation */
|
||||
writeValue(value: Endpoint[]) {
|
||||
}
|
||||
|
||||
registerOnChange(onChange: any) {
|
||||
this.onChange = onChange;
|
||||
}
|
||||
|
||||
registerOnTouched(_: any) {}
|
||||
|
||||
/* Validator implementation */
|
||||
validate(control: AbstractControl): ValidationErrors | null {
|
||||
if (!this.form.valid) {
|
||||
return {'internal': true};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
registerOnValidatorChange?(onValidatorChange: () => void): void {
|
||||
this.onValidatorChange = onValidatorChange;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
div.group { margin-bottom: 16px; }
|
||||
.mid-width { width: 50%; }
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="group">
|
||||
<span *ngFor="let entry of entries; let i=index">
|
||||
<mat-form-field class="mid-width" appearance="outline">
|
||||
<mat-label><span>Name</span></mat-label>
|
||||
<input [attr.data-cy]="dataCyPrefix+'-name-'+i" matInput [value]="entry.name" (change)="onKeyChange(i, $event)" (input)="onKeyChange(i, $event)">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="mid-width" appearance="outline">
|
||||
<mat-label><span>Value</span></mat-label>
|
||||
<input [attr.data-cy]="dataCyPrefix+'-value-'+i" matInput [value]="entry.value" (change)="onValueChange(i, $event)" (input)="onValueChange(i, $event)">
|
||||
</mat-form-field>
|
||||
</span>
|
||||
<button [attr.data-cy]="dataCyPrefix+'-plus'" *ngIf="entries.length > 0" mat-icon-button (click)="addEntry()">
|
||||
<mat-icon class="tab-icon material-icons-outlined">add</mat-icon>
|
||||
</button>
|
||||
<button [attr.data-cy]="dataCyPrefix+'-add'" *ngIf="entries.length == 0" mat-flat-button (click)="addEntry()">{{addLabel}}</button>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MultiKeyValueComponent } from './multi-key-value.component';
|
||||
|
||||
describe('MultiKeyValueComponent', () => {
|
||||
let component: MultiKeyValueComponent;
|
||||
let fixture: ComponentFixture<MultiKeyValueComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ MultiKeyValueComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MultiKeyValueComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Component, Input, forwardRef } from '@angular/core';
|
||||
import { AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator, Validators } from '@angular/forms';
|
||||
|
||||
interface KeyValue {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-multi-key-value',
|
||||
templateUrl: './multi-key-value.component.html',
|
||||
styleUrls: ['./multi-key-value.component.css'],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
multi: true,
|
||||
useExisting: MultiKeyValueComponent
|
||||
},
|
||||
{
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => MultiKeyValueComponent),
|
||||
multi: true,
|
||||
},
|
||||
]
|
||||
})
|
||||
export class MultiKeyValueComponent implements ControlValueAccessor, Validator {
|
||||
|
||||
@Input() dataCyPrefix: string = "";
|
||||
@Input() addLabel: string = "";
|
||||
|
||||
onChange = (_: KeyValue[]) => {};
|
||||
onValidatorChange = () => {};
|
||||
|
||||
entries: KeyValue[] = [];
|
||||
|
||||
writeValue(value: KeyValue[]) {
|
||||
this.entries = value;
|
||||
}
|
||||
|
||||
registerOnChange(onChange: any) {
|
||||
this.onChange = onChange;
|
||||
}
|
||||
|
||||
registerOnTouched(_: any) {}
|
||||
|
||||
addEntry() {
|
||||
this.entries.push({name: "", value: ""});
|
||||
this.onChange(this.entries);
|
||||
}
|
||||
|
||||
onKeyChange(i: number, e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
this.entries[i].name = target.value;
|
||||
this.onChange(this.entries);
|
||||
}
|
||||
|
||||
onValueChange(i: number, e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
this.entries[i].value = target.value;
|
||||
this.onChange(this.entries);
|
||||
}
|
||||
|
||||
/* Validator implementation */
|
||||
validate(control: AbstractControl): ValidationErrors | null {
|
||||
for (let i=0; i<this.entries.length; i++) {
|
||||
const entry = this.entries[i];
|
||||
if (entry.name == "" || entry.value == "") {
|
||||
return {'internal': true};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
registerOnValidatorChange?(onValidatorChange: () => void): void {
|
||||
this.onValidatorChange = onValidatorChange;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
useExisting: MultiTextComponent
|
||||
}
|
||||
]
|
||||
|
||||
})
|
||||
export class MultiTextComponent implements ControlValueAccessor {
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<h3>Volume Mounts</h3>
|
||||
<div class="group">
|
||||
<div *ngFor="let vm of volumeMounts; let i=index">
|
||||
<mat-form-field class="inline" appearance="outline">
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Volume, VolumeMount } from 'src/app/api-gen';
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => VolumeMountsComponent),
|
||||
multi: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
export class VolumeMountsComponent implements Validator {
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
.main { padding: 16px; }
|
||||
mat-form-field.full-width { width: 100%; }
|
||||
mat-form-field.mid-width { width: 50%; }
|
||||
.mid-width { width: 50%; }
|
||||
.source-configuration-details {
|
||||
margin-left: 16px;
|
||||
}
|
||||
div.buttonbar {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.outbutton {
|
||||
text-align: right;
|
||||
}
|
||||
@@ -12,36 +12,81 @@
|
||||
<input placeholder="Image to start the container" data-cy="container-image" matInput formControlName="image">
|
||||
</mat-form-field>
|
||||
<h3>Command and Arguments</h3>
|
||||
<div class="description">Command and Arguments can be used to override the entrypoint of the image</div>
|
||||
<app-multi-text formControlName="command" label="Command" addLabel="Add command"></app-multi-text>
|
||||
<app-multi-text formControlName="args" label="Arg" addLabel="Add arg"></app-multi-text>
|
||||
|
||||
<h3>Environment Variables</h3>
|
||||
<div class="description">Environment Variables to define in the running container</div>
|
||||
<app-multi-key-value dataCyPrefix="container-env" addLabel="Add Environment Variable" formControlName="env"></app-multi-key-value>
|
||||
|
||||
<h3>Volume Mounts</h3>
|
||||
<div class="description">Volumes to mount into the container's filesystem</div>
|
||||
<app-volume-mounts
|
||||
[volumes]="volumeNames"
|
||||
formControlName="volumeMounts"
|
||||
(createNewVolume)="onCreateNewVolume($event)"></app-volume-mounts>
|
||||
|
||||
<h3>Resource Usage</h3>
|
||||
<mat-form-field appearance="outline" class="mid-width">
|
||||
<mat-label><span>Memory Request</span></mat-label>
|
||||
<mat-error>{{quantityErrMsgMemory}}</mat-error>
|
||||
<input placeholder="memory requested for the container. Ex: 1Gi" data-cy="container-memory-request" matInput formControlName="memoryRequest">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mid-width">
|
||||
<mat-label><span>Memory Limit</span></mat-label>
|
||||
<mat-error>{{quantityErrMsgMemory}}</mat-error>
|
||||
<input placeholder="memory limit for the container. Ex: 1Gi" data-cy="container-memory-limit" matInput formControlName="memoryLimit">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mid-width">
|
||||
<mat-label><span>CPU Request</span></mat-label>
|
||||
<mat-error>{{quantityErrMsgCPU}}</mat-error>
|
||||
<input placeholder="CPU requested for the container. Ex: 500m" data-cy="container-cpu-request" matInput formControlName="cpuRequest">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mid-width">
|
||||
<mat-label><span>CPU Limit</span></mat-label>
|
||||
<mat-error>{{quantityErrMsgCPU}}</mat-error>
|
||||
<input placeholder="CPU limit for the container. Ex: 1" data-cy="container-cpu-limit" matInput formControlName="cpuLimit">
|
||||
</mat-form-field>
|
||||
|
||||
<h3>Endpoints</h3>
|
||||
<div class="description">Endpoints exposed by the container</div>
|
||||
<app-endpoints formControlName="endpoints"></app-endpoints>
|
||||
|
||||
<div class="outbutton"><button data-cy="container-more-params" *ngIf="!seeMore" mat-flat-button (click)="more()">More parameters...</button></div>
|
||||
|
||||
<div *ngIf="seeMore">
|
||||
|
||||
<h3>Resource Usage</h3>
|
||||
<div class="description">CPU and Memory resources necessary for container's execution</div>
|
||||
<mat-form-field appearance="outline" class="mid-width">
|
||||
<mat-label><span>Memory Request</span></mat-label>
|
||||
<mat-error>{{quantityErrMsgMemory}}</mat-error>
|
||||
<input placeholder="memory requested for the container. Ex: 1Gi" data-cy="container-memory-request" matInput formControlName="memoryRequest">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mid-width">
|
||||
<mat-label><span>Memory Limit</span></mat-label>
|
||||
<mat-error>{{quantityErrMsgMemory}}</mat-error>
|
||||
<input placeholder="memory limit for the container. Ex: 1Gi" data-cy="container-memory-limit" matInput formControlName="memoryLimit">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mid-width">
|
||||
<mat-label><span>CPU Request</span></mat-label>
|
||||
<mat-error>{{quantityErrMsgCPU}}</mat-error>
|
||||
<input placeholder="CPU requested for the container. Ex: 500m" data-cy="container-cpu-request" matInput formControlName="cpuRequest">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mid-width">
|
||||
<mat-label><span>CPU Limit</span></mat-label>
|
||||
<mat-error>{{quantityErrMsgCPU}}</mat-error>
|
||||
<input placeholder="CPU limit for the container. Ex: 1" data-cy="container-cpu-limit" matInput formControlName="cpuLimit">
|
||||
</mat-form-field>
|
||||
|
||||
<h3>Sources</h3>
|
||||
<div class="description">Declare if and how sources are mounted into the container's filesystem. By default, sources are automatically mounted into $PROJECTS_ROOT or /projects directory</div>
|
||||
<div><mat-checkbox data-cy="container-sources-configuration" formControlName="configureSources">Configure Source mount</mat-checkbox></div>
|
||||
<div *ngIf="form.get('configureSources')?.value" class="source-configuration-details">
|
||||
<div style="display: inline-flex" class="mid-width">
|
||||
<mat-checkbox data-cy="container-mount-sources" formControlName="mountSources">Mount sources into container</mat-checkbox>
|
||||
<mat-checkbox data-cy="container-sources-specific-directory" matTooltip="${PROJECTS_ROOT} or /projects by default" formControlName="_specificDir">Into specific directory</mat-checkbox>
|
||||
</div>
|
||||
<mat-form-field appearance="outline" class="mid-width">
|
||||
<mat-label><span>Mount sources into</span></mat-label>
|
||||
<input placeholder="Container's directory on which to mount sources" data-cy="container-source-mapping" matInput formControlName="sourceMapping">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<h3>Deployment Annotations</h3>
|
||||
<div class="description">Annotations added to the Kubernetes Deployment created for running this container</div>
|
||||
<app-multi-key-value dataCyPrefix="container-deploy-anno" addLabel="Add Annotation" formControlName="deployAnnotations"></app-multi-key-value>
|
||||
|
||||
<h3>Service Annotations</h3>
|
||||
<div class="description">Annotations added to the Kubernetes Service created for accessing this container</div>
|
||||
<app-multi-key-value dataCyPrefix="container-svc-anno" addLabel="Add Annotation" formControlName="svcAnnotations"></app-multi-key-value>
|
||||
</div>
|
||||
|
||||
<div class="outbutton"><button data-cy="container-less-params" *ngIf="seeMore" mat-flat-button (click)="less()">Less parameters...</button></div>
|
||||
</form>
|
||||
|
||||
<button data-cy="container-create" [disabled]="form.invalid" mat-flat-button color="primary" matTooltip="create new container" (click)="create()">Create</button>
|
||||
<button *ngIf="cancelable" mat-flat-button (click)="cancel()">Cancel</button>
|
||||
<div class="buttonbar">
|
||||
<button data-cy="container-create" [disabled]="form.invalid" mat-flat-button color="primary" matTooltip="create new container" (click)="create()">Create</button>
|
||||
<button *ngIf="cancelable" mat-flat-button (click)="cancel()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,6 +27,7 @@ export class ContainerComponent {
|
||||
quantityErrMsgCPU = 'Numeric value, with optional unit m, k, M, G, T, P, E';
|
||||
|
||||
volumesToCreate: Volume[] = [];
|
||||
seeMore: boolean = false;
|
||||
|
||||
constructor(
|
||||
private devstate: DevstateService,
|
||||
@@ -37,16 +38,59 @@ export class ContainerComponent {
|
||||
image: new FormControl("", [Validators.required]),
|
||||
command: new FormControl([]),
|
||||
args: new FormControl([]),
|
||||
env: new FormControl([]),
|
||||
volumeMounts: new FormControl([]),
|
||||
memoryRequest: new FormControl("", null, [this.devstate.isQuantity()]),
|
||||
memoryLimit: new FormControl("", null, [this.devstate.isQuantity()]),
|
||||
cpuRequest: new FormControl("", null, [this.devstate.isQuantity()]),
|
||||
cpuLimit: new FormControl("", null, [this.devstate.isQuantity()]),
|
||||
volumeMounts: new FormControl([]),
|
||||
})
|
||||
configureSources: new FormControl(false),
|
||||
mountSources: new FormControl(true),
|
||||
_specificDir: new FormControl(false),
|
||||
sourceMapping: new FormControl(""),
|
||||
deployAnnotations: new FormControl([]),
|
||||
svcAnnotations: new FormControl([]),
|
||||
endpoints: new FormControl([]),
|
||||
});
|
||||
|
||||
this.form.valueChanges.subscribe((value: any) => {
|
||||
this.updateSourceFields(value);
|
||||
});
|
||||
this.updateSourceFields(this.form.value);
|
||||
}
|
||||
|
||||
updateSourceFields(value: any) {
|
||||
const sourceMappingEnabled = value.mountSources && value._specificDir;
|
||||
if (!sourceMappingEnabled && !this.form.get('sourceMapping')?.disabled) {
|
||||
this.form.get('sourceMapping')?.disable();
|
||||
this.form.get('sourceMapping')?.setValue('');
|
||||
this.form.get('_specificDir')?.setValue(false);
|
||||
}
|
||||
if (sourceMappingEnabled && !this.form.get('sourceMapping')?.enabled ) {
|
||||
this.form.get('sourceMapping')?.enable();
|
||||
}
|
||||
|
||||
const specificDirEnabled = value.mountSources;
|
||||
if (!specificDirEnabled && !this.form.get('_specificDir')?.disabled) {
|
||||
this.form.get('_specificDir')?.disable();
|
||||
}
|
||||
if (specificDirEnabled && !this.form.get('_specificDir')?.enabled ) {
|
||||
this.form.get('_specificDir')?.enable();
|
||||
}
|
||||
}
|
||||
|
||||
create() {
|
||||
this.telemetry.track("[ui] create container");
|
||||
|
||||
const toObject = (o: {name: string, value: string}[]) => {
|
||||
return o.reduce((acc: any, val: {name: string, value: string}) => { acc[val.name] = val.value; return acc; }, {});
|
||||
};
|
||||
|
||||
const container = this.form.value;
|
||||
container.annotation = {
|
||||
deployment: toObject(container.deployAnnotations),
|
||||
service: toObject(container.svcAnnotations),
|
||||
};
|
||||
this.created.emit({
|
||||
container: this.form.value,
|
||||
volumes: this.volumesToCreate,
|
||||
@@ -60,4 +104,11 @@ export class ContainerComponent {
|
||||
onCreateNewVolume(v: Volume) {
|
||||
this.volumesToCreate.push(v);
|
||||
}
|
||||
|
||||
more() {
|
||||
this.seeMore = true;
|
||||
}
|
||||
less() {
|
||||
this.seeMore = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,20 @@ export class DevstateService {
|
||||
image: container.image,
|
||||
command: container.command,
|
||||
args: container.args,
|
||||
env: container.env,
|
||||
memReq: container.memoryRequest,
|
||||
memLimit: container.memoryLimit,
|
||||
cpuReq: container.cpuRequest,
|
||||
cpuLimit: container.cpuLimit,
|
||||
volumeMounts: container.volumeMounts,
|
||||
configureSources: container.configureSources,
|
||||
mountSources: container.mountSources,
|
||||
sourceMapping: container.sourceMapping,
|
||||
annotation: {
|
||||
deployment: container.annotation.deployment,
|
||||
service: container.annotation.service
|
||||
},
|
||||
endpoints: container.endpoints,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,3 +14,26 @@ mat-card-content { padding: 16px; }
|
||||
table.aligned > tr > td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
div.endpoint-list {
|
||||
display: float;
|
||||
}
|
||||
mat-card.endpoint {
|
||||
width: fit-content;
|
||||
float: left;
|
||||
margin: 0 8px;
|
||||
}
|
||||
mat-card.endpoint mat-card-header {
|
||||
padding: 8px 8px 0 8px;
|
||||
}
|
||||
mat-card.endpoint mat-card-title {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
mat-card.endpoint mat-card-subtitle {
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
}
|
||||
mat-card.endpoint mat-card-content {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,14 @@
|
||||
<td>Args:</td>
|
||||
<td><code>{{container.args.join(" ")}}</code></td>
|
||||
</tr>
|
||||
<tr *ngIf="container.env.length">
|
||||
<td>Environment variables:</td>
|
||||
<td>
|
||||
<div *ngFor="let env of container.env">
|
||||
{{env.name}}: {{env.value}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="container.volumeMounts.length > 0">
|
||||
<td>Volume Mounts:</td>
|
||||
<td>
|
||||
@@ -44,6 +52,47 @@
|
||||
<td>CPU Limit:</td>
|
||||
<td><code>{{container.cpuLimit}}</code></td>
|
||||
</tr>
|
||||
<tr *ngIf="container.annotation.deployment">
|
||||
<td>Deployment Annotations:</td>
|
||||
<td>
|
||||
<div *ngFor="let anno of container.annotation.deployment | keyvalue">
|
||||
{{anno.key}}: {{anno.value}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="container.annotation.service">
|
||||
<td>Service Annotations:</td>
|
||||
<td>
|
||||
<div *ngFor="let anno of container.annotation.service | keyvalue">
|
||||
{{anno.key}}: {{anno.value}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="container.configureSources">
|
||||
<td>Mount Sources:</td>
|
||||
<td><code>{{container.mountSources ? "Yes" : "No"}}</code></td>
|
||||
</tr>
|
||||
<tr *ngIf="container.configureSources && container.mountSources && container.sourceMapping">
|
||||
<td>Mount Sources Into:</td>
|
||||
<td><code>{{container.sourceMapping}}</code></td>
|
||||
</tr>
|
||||
<tr *ngIf="container.endpoints.length">
|
||||
<td>Endpoints:</td>
|
||||
<td class="container-list">
|
||||
<mat-card class="endpoint" *ngFor="let ep of container.endpoints">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ep.name}}</mat-card-title>
|
||||
<mat-card-subtitle>{{ep.targetPort}}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div>exposure: {{ep.exposure ?? 'public'}}</div>
|
||||
<div>protocol: {{ep.protocol ?? 'http'}}</div>
|
||||
<div *ngIf="ep.secure">secure</div>
|
||||
<div *ngIf="ep.path">path: {{ep.path}}</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</mat-card-content>
|
||||
|
||||
@@ -25,6 +25,14 @@ h2:has(+.description) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #3f51b5;
|
||||
}
|
||||
|
||||
h3:has(+.description) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-style: italic;
|
||||
font-size: smaller;
|
||||
|
||||
Reference in New Issue
Block a user