[ui] Set AutoBuild and DeployByDefault (#7051)

* Get and display autoBuild / deployByDefault

* Set autoBuild / deployByDefault

* Update ui static files

* [api] Add orpahn field to Image/Resource

* Display more info about Build / Deploy at startup

* Update ui static files

* e2e tests

* Update ui static files

* Fix unit tests

* 3-states button for AutoBuild

* 3-states button for DeployByDefault

* static ui files
This commit is contained in:
Philippe Martin
2023-09-01 10:18:49 +02:00
committed by GitHub
parent 7ff38b7965
commit e9dbded83b
30 changed files with 335 additions and 24 deletions

View File

@@ -676,6 +676,12 @@ paths:
type: boolean type: boolean
uri: uri:
type: string type: string
autoBuild:
type: string
enum:
- never
- undefined
- always
responses: responses:
'200': '200':
description: image was successfully added to the devfile description: image was successfully added to the devfile
@@ -769,6 +775,12 @@ paths:
type: string type: string
uri: uri:
type: string type: string
deployByDefault:
type: string
enum:
- never
- undefined
- always
responses: responses:
'200': '200':
description: resource was successfully added to the devfile description: resource was successfully added to the devfile
@@ -1699,6 +1711,8 @@ components:
- buildContext - buildContext
- rootRequired - rootRequired
- uri - uri
- autoBuild
- orphan
properties: properties:
name: name:
type: string type: string
@@ -1714,10 +1728,21 @@ components:
type: boolean type: boolean
uri: uri:
type: string type: string
autoBuild:
type: string
enum:
- never
- undefined
- always
orphan:
description: true if the image is not referenced in any command
type: boolean
Resource: Resource:
type: object type: object
required: required:
- name - name
- deployByDefault
- orphan
properties: properties:
name: name:
type: string type: string
@@ -1725,6 +1750,15 @@ components:
type: string type: string
uri: uri:
type: string type: string
deployByDefault:
type: string
enum:
- never
- undefined
- always
orphan:
description: true if the resource is not referenced in any command
type: boolean
Volume: Volume:
type: object type: object
required: required:

View File

@@ -23,6 +23,8 @@ type DevstateImagePostRequest struct {
RootRequired bool `json:"rootRequired,omitempty"` RootRequired bool `json:"rootRequired,omitempty"`
Uri string `json:"uri,omitempty"` Uri string `json:"uri,omitempty"`
AutoBuild string `json:"autoBuild,omitempty"`
} }
// AssertDevstateImagePostRequestRequired checks if the required fields are not zero-ed // AssertDevstateImagePostRequestRequired checks if the required fields are not zero-ed

View File

@@ -17,6 +17,8 @@ type DevstateResourcePostRequest struct {
Inlined string `json:"inlined,omitempty"` Inlined string `json:"inlined,omitempty"`
Uri string `json:"uri,omitempty"` Uri string `json:"uri,omitempty"`
DeployByDefault string `json:"deployByDefault,omitempty"`
} }
// AssertDevstateResourcePostRequestRequired checks if the required fields are not zero-ed // AssertDevstateResourcePostRequestRequired checks if the required fields are not zero-ed

View File

@@ -21,6 +21,11 @@ type Image struct {
RootRequired bool `json:"rootRequired"` RootRequired bool `json:"rootRequired"`
Uri string `json:"uri"` Uri string `json:"uri"`
AutoBuild string `json:"autoBuild"`
// true if the image is not referenced in any command
Orphan bool `json:"orphan"`
} }
// AssertImageRequired checks if the required fields are not zero-ed // AssertImageRequired checks if the required fields are not zero-ed
@@ -32,6 +37,8 @@ func AssertImageRequired(obj Image) error {
"buildContext": obj.BuildContext, "buildContext": obj.BuildContext,
"rootRequired": obj.RootRequired, "rootRequired": obj.RootRequired,
"uri": obj.Uri, "uri": obj.Uri,
"autoBuild": obj.AutoBuild,
"orphan": obj.Orphan,
} }
for name, el := range elements { for name, el := range elements {
if isZero := IsZeroValue(el); isZero { if isZero := IsZeroValue(el); isZero {

View File

@@ -15,12 +15,19 @@ type Resource struct {
Inlined string `json:"inlined,omitempty"` Inlined string `json:"inlined,omitempty"`
Uri string `json:"uri,omitempty"` Uri string `json:"uri,omitempty"`
DeployByDefault string `json:"deployByDefault"`
// true if the resource is not referenced in any command
Orphan bool `json:"orphan"`
} }
// AssertResourceRequired checks if the required fields are not zero-ed // AssertResourceRequired checks if the required fields are not zero-ed
func AssertResourceRequired(obj Resource) error { func AssertResourceRequired(obj Resource) error {
elements := map[string]interface{}{ elements := map[string]interface{}{
"name": obj.Name, "name": obj.Name,
"deployByDefault": obj.DeployByDefault,
"orphan": obj.Orphan,
} }
for name, el := range elements { for name, el := range elements {
if isZero := IsZeroValue(el); isZero { if isZero := IsZeroValue(el); isZero {

View File

@@ -53,6 +53,7 @@ func (s *DevstateApiService) DevstateImagePost(ctx context.Context, image openap
image.BuildContext, image.BuildContext,
image.RootRequired, image.RootRequired,
image.Uri, image.Uri,
image.AutoBuild,
) )
if err != nil { if err != nil {
return openapi.Response(http.StatusInternalServerError, openapi.GeneralError{ return openapi.Response(http.StatusInternalServerError, openapi.GeneralError{
@@ -77,6 +78,7 @@ func (s *DevstateApiService) DevstateResourcePost(ctx context.Context, resource
resource.Name, resource.Name,
resource.Inlined, resource.Inlined,
resource.Uri, resource.Uri,
resource.DeployByDefault,
) )
if err != nil { if err != nil {
return openapi.Response(http.StatusInternalServerError, openapi.GeneralError{ return openapi.Response(http.StatusInternalServerError, openapi.GeneralError{

View File

@@ -157,7 +157,7 @@ func TestDevfileState_AddApplyCommand(t *testing.T) {
_, err := state.AddImage( _, err := state.AddImage(
"an-image", "an-image",
"an-image-name", "an-image-name",
nil, "/context", false, "", nil, "/context", false, "", "undefined",
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -198,6 +198,7 @@ schemaVersion: 2.2.0
Name: "an-image", Name: "an-image",
ImageName: "an-image-name", ImageName: "an-image-name",
BuildContext: "/context", BuildContext: "/context",
AutoBuild: "undefined",
}, },
}, },
Resources: []Resource{}, Resources: []Resource{},

View File

@@ -7,6 +7,7 @@ import (
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
. "github.com/redhat-developer/odo/pkg/apiserver-gen/go" . "github.com/redhat-developer/odo/pkg/apiserver-gen/go"
"k8s.io/utils/pointer"
) )
func (o *DevfileState) AddContainer( func (o *DevfileState) AddContainer(
@@ -129,7 +130,7 @@ func (o *DevfileState) checkContainerUsed(name string) error {
return nil return nil
} }
func (o *DevfileState) AddImage(name string, imageName string, args []string, buildContext string, rootRequired bool, uri string) (DevfileContent, error) { func (o *DevfileState) AddImage(name string, imageName string, args []string, buildContext string, rootRequired bool, uri string, autoBuild string) (DevfileContent, error) {
container := v1alpha2.Component{ container := v1alpha2.Component{
Name: name, Name: name,
ComponentUnion: v1alpha2.ComponentUnion{ ComponentUnion: v1alpha2.ComponentUnion{
@@ -152,6 +153,11 @@ func (o *DevfileState) AddImage(name string, imageName string, args []string, bu
}, },
}, },
} }
if autoBuild == "never" {
container.Image.AutoBuild = pointer.Bool(false)
} else if autoBuild == "always" {
container.Image.AutoBuild = pointer.Bool(true)
}
err := o.Devfile.Data.AddComponents([]v1alpha2.Component{container}) err := o.Devfile.Data.AddComponents([]v1alpha2.Component{container})
if err != nil { if err != nil {
return DevfileContent{}, err return DevfileContent{}, err
@@ -192,7 +198,7 @@ func (o *DevfileState) checkImageUsed(name string) error {
return nil return nil
} }
func (o *DevfileState) AddResource(name string, inlined string, uri string) (DevfileContent, error) { func (o *DevfileState) AddResource(name string, inlined string, uri string, deployByDefault string) (DevfileContent, error) {
if inlined != "" && uri != "" { if inlined != "" && uri != "" {
return DevfileContent{}, errors.New("both inlined and uri cannot be set at the same time") return DevfileContent{}, errors.New("both inlined and uri cannot be set at the same time")
} }
@@ -209,6 +215,12 @@ func (o *DevfileState) AddResource(name string, inlined string, uri string) (Dev
}, },
}, },
} }
if deployByDefault == "never" {
container.Kubernetes.DeployByDefault = pointer.Bool(false)
} else if deployByDefault == "always" {
container.Kubernetes.DeployByDefault = pointer.Bool(true)
}
err := o.Devfile.Data.AddComponents([]v1alpha2.Component{container}) err := o.Devfile.Data.AddComponents([]v1alpha2.Component{container})
if err != nil { if err != nil {
return DevfileContent{}, err return DevfileContent{}, err

View File

@@ -311,6 +311,7 @@ func TestDevfileState_AddImage(t *testing.T) {
buildContext string buildContext string
rootRequired bool rootRequired bool
uri string uri string
autoBuild string
} }
tests := []struct { tests := []struct {
name string name string
@@ -331,6 +332,7 @@ func TestDevfileState_AddImage(t *testing.T) {
buildContext: "path/to/context", buildContext: "path/to/context",
rootRequired: true, rootRequired: true,
uri: "an-uri", uri: "an-uri",
autoBuild: "undefined",
}, },
want: DevfileContent{ want: DevfileContent{
Content: `components: Content: `components:
@@ -357,6 +359,8 @@ schemaVersion: 2.2.0
BuildContext: "path/to/context", BuildContext: "path/to/context",
RootRequired: true, RootRequired: true,
Uri: "an-uri", Uri: "an-uri",
Orphan: true,
AutoBuild: "undefined",
}, },
}, },
Resources: []Resource{}, Resources: []Resource{},
@@ -369,7 +373,7 @@ schemaVersion: 2.2.0
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
o := tt.state() o := tt.state()
got, err := o.AddImage(tt.args.name, tt.args.imageName, tt.args.args, tt.args.buildContext, tt.args.rootRequired, tt.args.uri) got, err := o.AddImage(tt.args.name, tt.args.imageName, tt.args.args, tt.args.buildContext, tt.args.rootRequired, tt.args.uri, tt.args.autoBuild)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("DevfileState.AddImage() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("DevfileState.AddImage() error = %v, wantErr %v", err, tt.wantErr)
return return
@@ -406,6 +410,7 @@ func TestDevfileState_DeleteImage(t *testing.T) {
"path/to/context", "path/to/context",
true, true,
"an-uri", "an-uri",
"undefined",
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -438,6 +443,7 @@ schemaVersion: 2.2.0
"path/to/context", "path/to/context",
true, true,
"an-uri", "an-uri",
"undefined",
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -472,9 +478,10 @@ schemaVersion: 2.2.0
func TestDevfileState_AddResource(t *testing.T) { func TestDevfileState_AddResource(t *testing.T) {
type args struct { type args struct {
name string name string
inline string inline string
uri string uri string
deployByDefault string
} }
tests := []struct { tests := []struct {
name string name string
@@ -505,8 +512,10 @@ schemaVersion: 2.2.0
Images: []Image{}, Images: []Image{},
Resources: []Resource{ Resources: []Resource{
{ {
Name: "a-name", Name: "a-name",
Uri: "an-uri", Uri: "an-uri",
Orphan: true,
DeployByDefault: "undefined",
}, },
}, },
Volumes: []Volume{}, Volumes: []Volume{},
@@ -535,8 +544,10 @@ schemaVersion: 2.2.0
Images: []Image{}, Images: []Image{},
Resources: []Resource{ Resources: []Resource{
{ {
Name: "a-name", Name: "a-name",
Inlined: "inline resource...", Inlined: "inline resource...",
Orphan: true,
DeployByDefault: "undefined",
}, },
}, },
Volumes: []Volume{}, Volumes: []Volume{},
@@ -548,7 +559,7 @@ schemaVersion: 2.2.0
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
o := tt.state() o := tt.state()
got, err := o.AddResource(tt.args.name, tt.args.inline, tt.args.uri) got, err := o.AddResource(tt.args.name, tt.args.inline, tt.args.uri, tt.args.deployByDefault)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("DevfileState.AddResource() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("DevfileState.AddResource() error = %v, wantErr %v", err, tt.wantErr)
return return
@@ -582,6 +593,7 @@ func TestDevfileState_Deleteresource(t *testing.T) {
"a-name", "a-name",
"", "",
"an-uri", "an-uri",
"undefined",
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -611,6 +623,7 @@ schemaVersion: 2.2.0
"a-name", "a-name",
"", "",
"an-uri", "an-uri",
"undefined",
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@@ -9,6 +9,7 @@ import (
"github.com/devfile/api/v2/pkg/devfile" "github.com/devfile/api/v2/pkg/devfile"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
. "github.com/redhat-developer/odo/pkg/apiserver-gen/go" . "github.com/redhat-developer/odo/pkg/apiserver-gen/go"
"github.com/redhat-developer/odo/pkg/libdevfile"
"k8s.io/utils/pointer" "k8s.io/utils/pointer"
) )
@@ -230,6 +231,15 @@ func (o *DevfileState) getEnv(envs []v1alpha2.EnvVar) []Env {
} }
func (o *DevfileState) getImages() ([]Image, error) { func (o *DevfileState) getImages() ([]Image, error) {
allApplyCommands, err := o.Devfile.Data.GetCommands(common.DevfileOptions{
CommandOptions: common.CommandOptions{
CommandType: v1alpha2.ApplyCommandType,
},
})
if err != nil {
return nil, err
}
images, err := o.Devfile.Data.GetComponents(common.DevfileOptions{ images, err := o.Devfile.Data.GetComponents(common.DevfileOptions{
ComponentOptions: common.ComponentOptions{ ComponentOptions: common.ComponentOptions{
ComponentType: v1alpha2.ImageComponentType, ComponentType: v1alpha2.ImageComponentType,
@@ -247,12 +257,33 @@ func (o *DevfileState) getImages() ([]Image, error) {
BuildContext: image.Image.Dockerfile.BuildContext, BuildContext: image.Image.Dockerfile.BuildContext,
RootRequired: pointer.BoolDeref(image.Image.Dockerfile.RootRequired, false), RootRequired: pointer.BoolDeref(image.Image.Dockerfile.RootRequired, false),
Uri: image.Image.Dockerfile.Uri, Uri: image.Image.Dockerfile.Uri,
AutoBuild: getThreeState(image.Image.AutoBuild),
Orphan: !libdevfile.IsComponentReferenced(allApplyCommands, image.Name),
}) })
} }
return result, nil return result, nil
} }
func getThreeState(v *bool) string {
if v == nil {
return "undefined"
}
if *v {
return "always"
}
return "never"
}
func (o *DevfileState) getResources() ([]Resource, error) { func (o *DevfileState) getResources() ([]Resource, error) {
allApplyCommands, err := o.Devfile.Data.GetCommands(common.DevfileOptions{
CommandOptions: common.CommandOptions{
CommandType: v1alpha2.ApplyCommandType,
},
})
if err != nil {
return nil, err
}
resources, err := o.Devfile.Data.GetComponents(common.DevfileOptions{ resources, err := o.Devfile.Data.GetComponents(common.DevfileOptions{
ComponentOptions: common.ComponentOptions{ ComponentOptions: common.ComponentOptions{
ComponentType: v1alpha2.KubernetesComponentType, ComponentType: v1alpha2.KubernetesComponentType,
@@ -264,9 +295,11 @@ func (o *DevfileState) getResources() ([]Resource, error) {
result := make([]Resource, 0, len(resources)) result := make([]Resource, 0, len(resources))
for _, resource := range resources { for _, resource := range resources {
result = append(result, Resource{ result = append(result, Resource{
Name: resource.Name, Name: resource.Name,
Inlined: resource.ComponentUnion.Kubernetes.Inlined, Inlined: resource.ComponentUnion.Kubernetes.Inlined,
Uri: resource.ComponentUnion.Kubernetes.Uri, Uri: resource.ComponentUnion.Kubernetes.Uri,
DeployByDefault: getThreeState(resource.ComponentUnion.Kubernetes.DeployByDefault),
Orphan: !libdevfile.IsComponentReferenced(allApplyCommands, resource.Name),
}) })
} }
return result, nil return result, nil

View File

@@ -34,6 +34,7 @@ func (o *DevfileState) SetDevfileContent(content string) (DevfileContent, error)
parserArgs := parser.ParserArgs{ parserArgs := parser.ParserArgs{
Data: []byte(content), Data: []byte(content),
ConvertKubernetesContentInUri: pointer.Bool(false), ConvertKubernetesContentInUri: pointer.Bool(false),
SetBooleanDefaults: pointer.Bool(false),
} }
var err error var err error
devfile, _, err := devfile.ParseDevfileAndValidate(parserArgs) devfile, _, err := devfile.ParseDevfileAndValidate(parserArgs)

View File

@@ -676,6 +676,12 @@ paths:
type: boolean type: boolean
uri: uri:
type: string type: string
autoBuild:
type: string
enum:
- never
- undefined
- always
responses: responses:
'200': '200':
description: image was successfully added to the devfile description: image was successfully added to the devfile
@@ -769,6 +775,12 @@ paths:
type: string type: string
uri: uri:
type: string type: string
deployByDefault:
type: string
enum:
- never
- undefined
- always
responses: responses:
'200': '200':
description: resource was successfully added to the devfile description: resource was successfully added to the devfile
@@ -1699,6 +1711,8 @@ components:
- buildContext - buildContext
- rootRequired - rootRequired
- uri - uri
- autoBuild
- orphan
properties: properties:
name: name:
type: string type: string
@@ -1714,10 +1728,21 @@ components:
type: boolean type: boolean
uri: uri:
type: string type: string
autoBuild:
type: string
enum:
- never
- undefined
- always
orphan:
description: true if the image is not referenced in any command
type: boolean
Resource: Resource:
type: object type: object
required: required:
- name - name
- deployByDefault
- orphan
properties: properties:
name: name:
type: string type: string
@@ -1725,6 +1750,15 @@ components:
type: string type: string
uri: uri:
type: string type: string
deployByDefault:
type: string
enum:
- never
- undefined
- always
orphan:
description: true if the resource is not referenced in any command
type: boolean
Volume: Volume:
type: object type: object
required: required:

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -43,7 +43,7 @@ func newComponent(devfileObj parser.DevfileObj, devfileCmp v1alpha2.Component) (
return cmp, nil return cmp, nil
} }
func isComponentReferenced(allApplyCommands []v1alpha2.Command, cmpName string) bool { func IsComponentReferenced(allApplyCommands []v1alpha2.Command, cmpName string) bool {
for _, cmd := range allApplyCommands { for _, cmd := range allApplyCommands {
if cmd.Apply == nil { if cmd.Apply == nil {
continue continue

View File

@@ -52,7 +52,7 @@ func GetImageComponentsToPushAutomatically(devfileObj parser.DevfileObj) ([]v1al
var add bool var add bool
if comp.Image.AutoBuild == nil { if comp.Image.AutoBuild == nil {
// auto-created only if not referenced by any apply command // auto-created only if not referenced by any apply command
if !isComponentReferenced(allApplyCommands, comp.Name) { if !IsComponentReferenced(allApplyCommands, comp.Name) {
add = true add = true
} }
} else if *comp.Image.AutoBuild { } else if *comp.Image.AutoBuild {

View File

@@ -68,11 +68,11 @@ func GetK8sAndOcComponentsToPush(devfileObj parser.DevfileObj, allowApply bool)
k = comp.Openshift.K8sLikeComponent k = comp.Openshift.K8sLikeComponent
} }
var add bool var add bool
if allowApply && isComponentReferenced(allApplyCommands, comp.Name) { if allowApply && IsComponentReferenced(allApplyCommands, comp.Name) {
add = true add = true
} else if k.DeployByDefault == nil { } else if k.DeployByDefault == nil {
// auto-created only if not referenced by any apply command // auto-created only if not referenced by any apply command
if !isComponentReferenced(allApplyCommands, comp.Name) { if !IsComponentReferenced(allApplyCommands, comp.Name) {
add = true add = true
} }
} else if *k.DeployByDefault { } else if *k.DeployByDefault {

View File

@@ -175,6 +175,39 @@ describe('devfile editor spec', () => {
.should('contain.text', 'an-image-name') .should('contain.text', 'an-image-name')
.should('contain.text', '/path/to/build/context') .should('contain.text', '/path/to/build/context')
.should('contain.text', '/path/to/dockerfile'); .should('contain.text', '/path/to/dockerfile');
cy.getByDataCy('image-build-startup').first()
.should('contain.text', 'Yes, the image is not referenced by any command');
});
it('displays a created image with forced build', () => {
cy.init();
cy.selectTab(TAB_IMAGES);
cy.getByDataCy('image-name').type('created-image');
cy.getByDataCy('image-image-name').type('an-image-name');
cy.getByDataCy('image-build-context').type('/path/to/build/context');
cy.getByDataCy('image-dockerfile-uri').type('/path/to/dockerfile');
cy.getByDataCy('image-auto-build-always').click();
cy.getByDataCy('image-create').click();
cy.getByDataCy('image-build-startup').first()
.should('contain.text', 'Yes, forced');
});
it('displays a created image with disabled build', () => {
cy.init();
cy.selectTab(TAB_IMAGES);
cy.getByDataCy('image-name').type('created-image');
cy.getByDataCy('image-image-name').type('an-image-name');
cy.getByDataCy('image-build-context').type('/path/to/build/context');
cy.getByDataCy('image-dockerfile-uri').type('/path/to/dockerfile');
cy.getByDataCy('image-auto-build-never').click();
cy.getByDataCy('image-create').click();
cy.getByDataCy('image-build-startup').first()
.should('contain.text', 'No, disabled');
}); });
it('displays a created resource, with manifest', () => { it('displays a created resource, with manifest', () => {
@@ -189,7 +222,10 @@ describe('devfile editor spec', () => {
cy.getByDataCy('resource-info').first() cy.getByDataCy('resource-info').first()
.should('contain.text', 'created-resource') .should('contain.text', 'created-resource')
.should('contain.text', 'a-resource-manifest'); .should('contain.text', 'a-resource-manifest');
});
cy.getByDataCy('resource-deploy-startup').first()
.should('contain.text', 'Yes, the resource is not referenced by any command');
});
it('displays a created resource, with uri (default)', () => { it('displays a created resource, with uri (default)', () => {
cy.init(); cy.init();
@@ -205,6 +241,42 @@ describe('devfile editor spec', () => {
.should('contain.text', '/my/manifest.yaml'); .should('contain.text', '/my/manifest.yaml');
}); });
it('displays a created resource, with forced deploy', () => {
cy.init();
cy.selectTab(TAB_RESOURCES);
cy.getByDataCy('resource-name').type('created-resource');
cy.getByDataCy('resource-toggle-inlined').click();
cy.getByDataCy('resource-manifest').type('a-resource-manifest');
cy.getByDataCy('resource-auto-deploy-always').click();
cy.getByDataCy('resource-create').click();
cy.getByDataCy('resource-info').first()
.should('contain.text', 'created-resource')
.should('contain.text', 'a-resource-manifest');
cy.getByDataCy('resource-deploy-startup').first()
.should('contain.text', 'Yes, forced');
});
it('displays a created resource, with disabled deploy', () => {
cy.init();
cy.selectTab(TAB_RESOURCES);
cy.getByDataCy('resource-name').type('created-resource');
cy.getByDataCy('resource-toggle-inlined').click();
cy.getByDataCy('resource-manifest').type('a-resource-manifest');
cy.getByDataCy('resource-auto-deploy-never').click();
cy.getByDataCy('resource-create').click();
cy.getByDataCy('resource-info').first()
.should('contain.text', 'created-resource')
.should('contain.text', 'a-resource-manifest');
cy.getByDataCy('resource-deploy-startup').first()
.should('contain.text', 'No, disabled');
});
it('displays a created volume', () => { it('displays a created volume', () => {
cy.init(); cy.init();
@@ -327,6 +399,9 @@ describe('devfile editor spec', () => {
.should('contain.text', 'an-image-name') .should('contain.text', 'an-image-name')
.should('contain.text', '/context/dir') .should('contain.text', '/context/dir')
.should('contain.text', '/path/to/Dockerfile'); .should('contain.text', '/path/to/Dockerfile');
cy.getByDataCy('image-build-startup').first()
.should('contain.text', 'No, the image is referenced by a command');
}); });
it('creates an apply resource command with a new resource using manifest', () => { it('creates an apply resource command with a new resource using manifest', () => {
@@ -353,6 +428,9 @@ describe('devfile editor spec', () => {
cy.getByDataCy('resource-info').first() cy.getByDataCy('resource-info').first()
.should('contain.text', 'a-created-resource') .should('contain.text', 'a-created-resource')
.should('contain.text', 'spec: {}'); .should('contain.text', 'spec: {}');
cy.getByDataCy('resource-deploy-startup').first()
.should('contain.text', 'No, the resource is referenced by a command');
}); });
it('creates an apply resource command with a new resource using uri (default)', () => { it('creates an apply resource command with a new resource using uri (default)', () => {

View File

@@ -21,5 +21,15 @@ export interface DevstateImagePostRequest {
buildContext?: string; buildContext?: string;
rootRequired?: boolean; rootRequired?: boolean;
uri?: string; uri?: string;
autoBuild?: DevstateImagePostRequest.AutoBuildEnum;
}
export namespace DevstateImagePostRequest {
export type AutoBuildEnum = 'never' | 'undefined' | 'always';
export const AutoBuildEnum = {
Never: 'never' as AutoBuildEnum,
Undefined: 'undefined' as AutoBuildEnum,
Always: 'always' as AutoBuildEnum
};
} }

View File

@@ -18,5 +18,15 @@ export interface DevstateResourcePostRequest {
name?: string; name?: string;
inlined?: string; inlined?: string;
uri?: string; uri?: string;
deployByDefault?: DevstateResourcePostRequest.DeployByDefaultEnum;
}
export namespace DevstateResourcePostRequest {
export type DeployByDefaultEnum = 'never' | 'undefined' | 'always';
export const DeployByDefaultEnum = {
Never: 'never' as DeployByDefaultEnum,
Undefined: 'undefined' as DeployByDefaultEnum,
Always: 'always' as DeployByDefaultEnum
};
} }

View File

@@ -18,5 +18,19 @@ export interface Image {
buildContext: string; buildContext: string;
rootRequired: boolean; rootRequired: boolean;
uri: string; uri: string;
autoBuild: Image.AutoBuildEnum;
/**
* true if the image is not referenced in any command
*/
orphan: boolean;
}
export namespace Image {
export type AutoBuildEnum = 'never' | 'undefined' | 'always';
export const AutoBuildEnum = {
Never: 'never' as AutoBuildEnum,
Undefined: 'undefined' as AutoBuildEnum,
Always: 'always' as AutoBuildEnum
};
} }

View File

@@ -15,5 +15,19 @@ export interface Resource {
name: string; name: string;
inlined?: string; inlined?: string;
uri?: string; uri?: string;
deployByDefault: Resource.DeployByDefaultEnum;
/**
* true if the resource is not referenced in any command
*/
orphan: boolean;
}
export namespace Resource {
export type DeployByDefaultEnum = 'never' | 'undefined' | 'always';
export const DeployByDefaultEnum = {
Never: 'never' as DeployByDefaultEnum,
Undefined: 'undefined' as DeployByDefaultEnum,
Always: 'always' as DeployByDefaultEnum
};
} }

View File

@@ -1,3 +1,6 @@
.main { padding: 16px; } .main { padding: 16px; }
mat-form-field.full-width { width: 100%; } mat-form-field.full-width { width: 100%; }
mat-form-field.mid-width { width: 50%; } mat-form-field.mid-width { width: 50%; }
div.toggle-group-div {
margin: 16px 0;
}

View File

@@ -2,6 +2,13 @@
<h2>Add a new image</h2> <h2>Add a new image</h2>
<div class="description">An Image defines how to build a container image.</div> <div class="description">An Image defines how to build a container image.</div>
<form [formGroup]="form"> <form [formGroup]="form">
<div class="toggle-group-div">
Build at Startup: <mat-button-toggle-group formControlName="autoBuild">
<mat-button-toggle data-cy="image-auto-build-never" matTooltip="Do not build the image at startup" value="never">Never</mat-button-toggle>
<mat-button-toggle data-cy="image-auto-build-undefined" matTooltip="Build at startup only if the image is not referenced in any command" value="undefined">If Orphan</mat-button-toggle>
<mat-button-toggle data-cy="image-auto-build-always" matTooltip="Force building the image at startup" value="always">Always</mat-button-toggle>
</mat-button-toggle-group>
</div>
<mat-form-field appearance="outline" class="mid-width"> <mat-form-field appearance="outline" class="mid-width">
<mat-label><span>Name</span></mat-label> <mat-label><span>Name</span></mat-label>
<mat-error>Lowercase words separated by dashes. Ex: my-image</mat-error> <mat-error>Lowercase words separated by dashes. Ex: my-image</mat-error>

View File

@@ -26,6 +26,7 @@ export class ImageComponent {
buildContext: new FormControl(""), buildContext: new FormControl(""),
rootRequired: new FormControl(false), rootRequired: new FormControl(false),
uri: new FormControl("", [Validators.required]), uri: new FormControl("", [Validators.required]),
autoBuild: new FormControl("undefined"),
}) })
} }

View File

@@ -2,6 +2,13 @@
<h2>Add a new resource</h2> <h2>Add a new resource</h2>
<div class="description">A Resource defines a Kubernetes resource. Its definition can be given either by a URI pointing to a manifest file or by an inlined YAML manifest.</div> <div class="description">A Resource defines a Kubernetes resource. Its definition can be given either by a URI pointing to a manifest file or by an inlined YAML manifest.</div>
<form [formGroup]="form"> <form [formGroup]="form">
<div class="toggle-group-div">
Deploy at Startup: <mat-button-toggle-group formControlName="deployByDefault">
<mat-button-toggle data-cy="resource-auto-deploy-never" matTooltip="Do not deploy the resource at startup" value="never">Never</mat-button-toggle>
<mat-button-toggle data-cy="resource-auto-deploy-undefined" matTooltip="Deploy at startup only if the resource is not referenced in any command" value="undefined">If Orphan</mat-button-toggle>
<mat-button-toggle data-cy="resource-auto-deploy-always" matTooltip="Force deploying the resource at startup" value="always">Always</mat-button-toggle>
</mat-button-toggle-group>
</div>
<mat-form-field appearance="outline" class="mid-width"> <mat-form-field appearance="outline" class="mid-width">
<mat-label><span>Name</span></mat-label> <mat-label><span>Name</span></mat-label>
<mat-error>Lowercase words separated by dashes. Ex: my-resource</mat-error> <mat-error>Lowercase words separated by dashes. Ex: my-resource</mat-error>

View File

@@ -24,6 +24,7 @@ export class ResourceComponent {
name: new FormControl("", [Validators.required, Validators.pattern(PATTERN_COMPONENT_ID)]), name: new FormControl("", [Validators.required, Validators.pattern(PATTERN_COMPONENT_ID)]),
uri: new FormControl("", [Validators.required]), uri: new FormControl("", [Validators.required]),
inlined: new FormControl("", []), inlined: new FormControl("", []),
deployByDefault: new FormControl("undefined"),
}) })
} }

View File

@@ -43,7 +43,8 @@ export class DevstateService {
args: image.args, args: image.args,
buildContext: image.buildContext, buildContext: image.buildContext,
rootRequired: image.rootRequired, rootRequired: image.rootRequired,
uri: image.uri uri: image.uri,
autoBuild: image.autoBuild,
}); });
} }
@@ -52,6 +53,7 @@ export class DevstateService {
name: resource.name, name: resource.name,
inlined: resource.inlined, inlined: resource.inlined,
uri: resource.uri, uri: resource.uri,
deployByDefault: resource.deployByDefault,
}); });
} }

View File

@@ -6,6 +6,13 @@
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<table class="aligned"> <table class="aligned">
<tr data-cy="image-build-startup">
<td>Build at Startup:</td>
<td *ngIf="image.autoBuild == 'always'"><code>Yes, forced</code></td>
<td *ngIf="image.autoBuild == 'undefined' && image.orphan"><code>Yes, the image is not referenced by any command</code></td>
<td *ngIf="image.autoBuild == 'undefined' && !image.orphan"><code>No, the image is referenced by a command</code></td>
<td *ngIf="image.autoBuild == 'never'"><code>No, disabled</code></td>
</tr>
<tr> <tr>
<td>Image Name:</td> <td>Image Name:</td>
<td><code>{{image.imageName}}</code></td> <td><code>{{image.imageName}}</code></td>

View File

@@ -5,6 +5,15 @@
<mat-card-subtitle>Cluster Resource</mat-card-subtitle> <mat-card-subtitle>Cluster Resource</mat-card-subtitle>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<table class="aligned">
<tr data-cy="resource-deploy-startup">
<td>Deploy at Startup:</td>
<td *ngIf="resource.deployByDefault == 'always'"><code>Yes, forced</code></td>
<td *ngIf="resource.deployByDefault == 'undefined' && resource.orphan"><code>Yes, the resource is not referenced by any command</code></td>
<td *ngIf="resource.deployByDefault == 'undefined' && !resource.orphan"><code>No, the resource is referenced by a command</code></td>
<td *ngIf="resource.deployByDefault == 'never'"><code>No, disabled</code></td>
</tr>
</table>
<div *ngIf="resource.uri">URI: <code>{{resource.uri}}</code></div> <div *ngIf="resource.uri">URI: <code>{{resource.uri}}</code></div>
<div *ngIf="resource.inlined"><pre>{{resource.inlined}}</pre></div> <div *ngIf="resource.inlined"><pre>{{resource.inlined}}</pre></div>
</mat-card-content> </mat-card-content>