[ui] Edit container (#7077)

* [api] patch container

* [ui] edit container

* [ui] Initialize endpoint component

* e2e tests

* static ui files
This commit is contained in:
Philippe Martin
2023-09-08 11:48:53 +02:00
committed by GitHub
parent 56b868d16c
commit 1d96115c45
21 changed files with 903 additions and 73 deletions

View File

@@ -16,6 +16,7 @@ go/model__devstate_command__command_name__move_post_request.go
go/model__devstate_command__command_name__set_default_post_request.go
go/model__devstate_composite_command__command_name__patch_request.go
go/model__devstate_composite_command_post_request.go
go/model__devstate_container__container_name__patch_request.go
go/model__devstate_container_post_request.go
go/model__devstate_events_put_request.go
go/model__devstate_exec_command__command_name__patch_request.go

View File

@@ -41,6 +41,7 @@ type DevstateApiRouter interface {
DevstateCompositeCommandCommandNamePatch(http.ResponseWriter, *http.Request)
DevstateCompositeCommandPost(http.ResponseWriter, *http.Request)
DevstateContainerContainerNameDelete(http.ResponseWriter, *http.Request)
DevstateContainerContainerNamePatch(http.ResponseWriter, *http.Request)
DevstateContainerPost(http.ResponseWriter, *http.Request)
DevstateDevfileDelete(http.ResponseWriter, *http.Request)
DevstateDevfileGet(http.ResponseWriter, *http.Request)
@@ -90,6 +91,7 @@ type DevstateApiServicer interface {
DevstateCompositeCommandCommandNamePatch(context.Context, string, DevstateCompositeCommandCommandNamePatchRequest) (ImplResponse, error)
DevstateCompositeCommandPost(context.Context, DevstateCompositeCommandPostRequest) (ImplResponse, error)
DevstateContainerContainerNameDelete(context.Context, string) (ImplResponse, error)
DevstateContainerContainerNamePatch(context.Context, string, DevstateContainerContainerNamePatchRequest) (ImplResponse, error)
DevstateContainerPost(context.Context, DevstateContainerPostRequest) (ImplResponse, error)
DevstateDevfileDelete(context.Context) (ImplResponse, error)
DevstateDevfileGet(context.Context) (ImplResponse, error)

View File

@@ -110,6 +110,12 @@ func (c *DevstateApiController) Routes() Routes {
"/api/v1/devstate/container/{containerName}",
c.DevstateContainerContainerNameDelete,
},
{
"DevstateContainerContainerNamePatch",
strings.ToUpper("Patch"),
"/api/v1/devstate/container/{containerName}",
c.DevstateContainerContainerNamePatch,
},
{
"DevstateContainerPost",
strings.ToUpper("Post"),
@@ -431,6 +437,32 @@ func (c *DevstateApiController) DevstateContainerContainerNameDelete(w http.Resp
}
// DevstateContainerContainerNamePatch -
func (c *DevstateApiController) DevstateContainerContainerNamePatch(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
containerNameParam := params["containerName"]
devstateContainerContainerNamePatchRequestParam := DevstateContainerContainerNamePatchRequest{}
d := json.NewDecoder(r.Body)
d.DisallowUnknownFields()
if err := d.Decode(&devstateContainerContainerNamePatchRequestParam); err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
if err := AssertDevstateContainerContainerNamePatchRequestRequired(devstateContainerContainerNamePatchRequestParam); err != nil {
c.errorHandler(w, r, err, nil)
return
}
result, err := c.service.DevstateContainerContainerNamePatch(r.Context(), containerNameParam, devstateContainerContainerNamePatchRequestParam)
// If an error occurred, encode the error with the status code
if err != nil {
c.errorHandler(w, r, err, &result)
return
}
// If no error, encode the body and the result code
EncodeJSONResponse(result.Body, &result.Code, w)
}
// DevstateContainerPost -
func (c *DevstateApiController) DevstateContainerPost(w http.ResponseWriter, r *http.Request) {
devstateContainerPostRequestParam := DevstateContainerPostRequest{}

View File

@@ -0,0 +1,98 @@
/*
* odo dev
*
* API interface for 'odo dev'
*
* API version: 0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package openapi
type DevstateContainerContainerNamePatchRequest struct {
// Container image
Image string `json:"image"`
// Entrypoint of the container
Command []string `json:"command,omitempty"`
// 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"`
// Memory limit for the deployed container
MemLimit string `json:"memLimit,omitempty"`
// Requested CPU for the deployed container
CpuReq string `json:"cpuReq,omitempty"`
// CPU limit for the deployed container
CpuLimit string `json:"cpuLimit,omitempty"`
// 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"`
}
// AssertDevstateContainerContainerNamePatchRequestRequired checks if the required fields are not zero-ed
func AssertDevstateContainerContainerNamePatchRequestRequired(obj DevstateContainerContainerNamePatchRequest) error {
elements := map[string]interface{}{
"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
}
// AssertRecurseDevstateContainerContainerNamePatchRequestRequired recursively checks if required fields are not zero-ed in a nested slice.
// Accepts only nested slice of DevstateContainerContainerNamePatchRequest (e.g. [][]DevstateContainerContainerNamePatchRequest), otherwise ErrTypeAssertionError is thrown.
func AssertRecurseDevstateContainerContainerNamePatchRequestRequired(objSlice interface{}) error {
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
aDevstateContainerContainerNamePatchRequest, ok := obj.(DevstateContainerContainerNamePatchRequest)
if !ok {
return ErrTypeAssertionError
}
return AssertDevstateContainerContainerNamePatchRequestRequired(aDevstateContainerContainerNamePatchRequest)
})
}

View File

@@ -384,7 +384,33 @@ func (s *DevstateApiService) DevstateCompositeCommandCommandNamePatch(ctx contex
)
if err != nil {
return openapi.Response(http.StatusInternalServerError, openapi.GeneralError{
Message: fmt.Sprintf("Error updating the Image Command: %s", err),
Message: fmt.Sprintf("Error updating the Composite Command: %s", err),
}), nil
}
return openapi.Response(http.StatusOK, newContent), nil
}
func (s *DevstateApiService) DevstateContainerContainerNamePatch(ctx context.Context, name string, patch openapi.DevstateContainerContainerNamePatchRequest) (openapi.ImplResponse, error) {
newContent, err := s.devfileState.PatchContainer(
name,
patch.Image,
patch.Command,
patch.Args,
patch.Env,
patch.MemReq,
patch.MemLimit,
patch.CpuReq,
patch.CpuLimit,
patch.VolumeMounts,
patch.ConfigureSources,
patch.MountSources,
patch.SourceMapping,
patch.Annotation,
patch.Endpoints,
)
if err != nil {
return openapi.Response(http.StatusInternalServerError, openapi.GeneralError{
Message: fmt.Sprintf("Error updating the container: %s", err),
}), nil
}
return openapi.Response(http.StatusOK, newContent), nil

View File

@@ -27,44 +27,6 @@ func (o *DevfileState) AddContainer(
annotation Annotation,
endpoints []Endpoint,
) (DevfileContent, error) {
v1alpha2VolumeMounts := make([]v1alpha2.VolumeMount, 0, len(volumeMounts))
for _, vm := range volumeMounts {
v1alpha2VolumeMounts = append(v1alpha2VolumeMounts, v1alpha2.VolumeMount{
Name: vm.Name,
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,
@@ -74,15 +36,15 @@ func (o *DevfileState) AddContainer(
Image: image,
Command: command,
Args: args,
Env: v1alpha2Envs,
Env: tov1alpha2EnvVars(envs),
MemoryRequest: memRequest,
MemoryLimit: memLimit,
CpuRequest: cpuRequest,
CpuLimit: cpuLimit,
VolumeMounts: v1alpha2VolumeMounts,
Annotation: annotations,
VolumeMounts: tov1alpha2VolumeMounts(volumeMounts),
Annotation: tov1alpha2Annotation(annotation),
},
Endpoints: v1alpha2Endpoints,
Endpoints: tov1alpha2Endpoints(endpoints),
},
},
}
@@ -97,6 +59,115 @@ func (o *DevfileState) AddContainer(
return o.GetContent()
}
func (o *DevfileState) PatchContainer(
name string,
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) {
found, err := o.Devfile.Data.GetComponents(common.DevfileOptions{
ComponentOptions: common.ComponentOptions{
ComponentType: v1alpha2.ContainerComponentType,
},
FilterByName: name,
})
if err != nil {
return DevfileContent{}, err
}
if len(found) != 1 {
return DevfileContent{}, fmt.Errorf("%d Container found with name %q", len(found), name)
}
container := found[0]
container.Container.Image = image
container.Container.Command = command
container.Container.Args = args
container.Container.Env = tov1alpha2EnvVars(envs)
container.Container.MemoryRequest = memRequest
container.Container.MemoryLimit = memLimit
container.Container.CpuRequest = cpuRequest
container.Container.CpuLimit = cpuLimit
container.Container.VolumeMounts = tov1alpha2VolumeMounts(volumeMounts)
container.Container.MountSources = nil
container.Container.SourceMapping = ""
if configureSources {
container.Container.MountSources = &mountSources
container.Container.SourceMapping = sourceMapping
}
container.Container.Annotation = tov1alpha2Annotation(annotation)
container.Container.Endpoints = tov1alpha2Endpoints(endpoints)
err = o.Devfile.Data.UpdateComponent(container)
if err != nil {
return DevfileContent{}, err
}
return o.GetContent()
}
func tov1alpha2EnvVars(envs []Env) []v1alpha2.EnvVar {
result := make([]v1alpha2.EnvVar, 0, len(envs))
for _, env := range envs {
result = append(result, v1alpha2.EnvVar{
Name: env.Name,
Value: env.Value,
})
}
return result
}
func tov1alpha2VolumeMounts(volumeMounts []VolumeMount) []v1alpha2.VolumeMount {
result := make([]v1alpha2.VolumeMount, 0, len(volumeMounts))
for _, vm := range volumeMounts {
result = append(result, v1alpha2.VolumeMount{
Name: vm.Name,
Path: vm.Path,
})
}
return result
}
func tov1alpha2Annotation(annotation Annotation) *v1alpha2.Annotation {
var result *v1alpha2.Annotation
if len(annotation.Deployment) > 0 || len(annotation.Service) > 0 {
result = &v1alpha2.Annotation{}
if len(annotation.Deployment) > 0 {
result.Deployment = annotation.Deployment
}
if len(annotation.Service) > 0 {
result.Service = annotation.Service
}
}
return result
}
func tov1alpha2Endpoints(endpoints []Endpoint) []v1alpha2.Endpoint {
result := make([]v1alpha2.Endpoint, 0, len(endpoints))
for _, endpoint := range endpoints {
endpoint := endpoint
result = append(result, 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,
})
}
return result
}
func (o *DevfileState) DeleteContainer(name string) (DevfileContent, error) {
err := o.checkContainerUsed(name)

View File

@@ -649,6 +649,126 @@ paths:
example:
message: "Error deleting the container"
patch:
tags:
- devstate
description: Update a container
parameters:
- name: containerName
in: path
description: Container name to update
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
required:
- name
- image
properties:
image:
description: Container image
type: string
command:
description: Entrypoint of the container
type: array
items: {
type: string
}
args:
description: Args passed to the Container entrypoint
type: array
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
memLimit:
description: Memory limit for the deployed container
type: string
cpuReq:
description: Requested CPU for the deployed container
type: string
cpuLimit:
description: CPU limit for the deployed container
type: string
volumeMounts:
description: Volume to mount into the container filesystem
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 updated
content:
application/json:
schema:
$ref: '#/components/schemas/DevfileContent'
example:
{
"content": "schemaVersion: 2.2.0\n",
"commands": [],
"containers": [],
"images": [],
"resources": [],
"events": {
"preStart": null,
"postStart": null,
"preStop": null,
"postStop": null
},
"metadata": {
"name": "",
"version": "",
"displayName": "",
description": "",
"tags": "",
"architectures": "",
"icon": "",
"globalMemoryLimit": "",
"projectType": "",
"language": "",
"website": "",
"provider": "",
"supportUrl": ""
}
}
'500':
description: Error updating the container
content:
application/json:
schema:
$ref: '#/components/schemas/GeneralError'
example:
message: "Error updating the container"
/devstate/image:
post:
tags:

View File

@@ -11,6 +11,6 @@
<body class="mat-typography">
<div id="loading">Loading, please wait...</div>
<app-root></app-root>
<script src="runtime.1289ea0acffcdc5e.js" type="module"></script><script src="polyfills.8b3b37cedaf377c3.js" type="module"></script><script src="main.30e0dd4eb9375796.js" type="module"></script>
<script src="runtime.1289ea0acffcdc5e.js" type="module"></script><script src="polyfills.8b3b37cedaf377c3.js" type="module"></script><script src="main.d81b67faa7ca223c.js" type="module"></script>
</body></html>

File diff suppressed because one or more lines are too long