-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Delete Comp, Command, Proj, StarterProj #65
Conversation
Since delete functions are being added, I'm thinking if we should do validation after delete For example, if a volume component, which is being referenced in a container component, got deleted, the new devfile becomes invalid. Same for the add & update functions, should do validation before return. We might want to discuss in meeting for that special case |
Commenting purely from |
@yangcao77 When deleting a component we make sure the volume mounts are also deleted if its a volume. Similarly for command, we make sure the composite sub commands are also deleted if it matches the command to be deleted. As for if it needs to be validated, I think we can make a call to Validate() func after writing to YAML. Since this is an exported func, tools like odo can also call this and this may also help ensure the data being written is validated. But it maybe overkill because we anyways validate when parsing the devfile which happens during every operation. WDYT |
pkg/devfile/parser/data/interface.go
Outdated
DeleteVolume(name string) error | ||
GetVolumeMountPath(name string) (string, error) | ||
// volume mount related methods | ||
AddVolumeMount(componentName, name, path string) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AddVolumeMount(componentName, name, path string) error | |
AddVolumeMount(componentName string, volumeMount v1.volumeMount) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will make it an array AddVolumeMount(componentName string, volumeMounts []v1.volumeMount) error
since all existing add are arrays
func (d *DevfileV2) DeleteCommand(id string) error { | ||
|
||
found := false | ||
for i := len(d.Commands) - 1; i >= 0; i-- { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can use for index := range d.Commands
to find the index
and append(d.Commands[:index], d.Commands[index+1:]...)
same for other similar loops
wantErr bool | ||
}{ | ||
{ | ||
name: "Commands that belong to Composite Command", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename the test case to avoid confusion, as composite command is not a special case now
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
d := &DevfileV2{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we have the devObj defined outside? so we do not need to define commands in each of the test case.
and the test args can be
tests := []struct {
name string
commandToDelete string
wantCommands []v1.Command
wantErr bool
}
}, | ||
}, | ||
}, | ||
wantCommands: []v1.Command{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the error case, looks like that wantCommands
is not necessary
wantErr bool | ||
}{ | ||
{ | ||
name: "Volume Component with mounts", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we do pure deletion without considering the reference, we should rename the test case to avoid confusion
SubDir: "/project2", | ||
}, | ||
}, | ||
wantStarterProjects: []v1.StarterProject{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for the error case, wantStarterProjects do not need to be defined
func (d *DevfileV2) AddVolume(volumeComponent v1.Component, path string) error { | ||
volumeExists := false | ||
// AddVolumeMount adds the volume mount to the specified container component | ||
func (d *DevfileV2) AddVolumeMount(componentName, name, path string) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since other APIs take go struct if it's ever defined in devfile api, suggest to use v1.VolumeMount
to be consistent with other APIs
if !reflect.DeepEqual(d.Components, tt.wantComponents) { | ||
t.Errorf("wanted: %v, got: %v, difference at %v", tt.wantComponents, d.Components, pretty.Compare(tt.wantComponents, d.Components)) | ||
err := d.AddVolumeMount(tt.args.componentName, tt.args.name, tt.args.path) | ||
if tt.wantErr && err == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why split this logic (err != nil) != tt.wantErr
into two if-else blocks?
the existing one is clear enough to me
Same for other same changes.
} else if !tt.wantErr && err != nil { | ||
t.Errorf("Got unexpected error: %s", err) | ||
} else if err == nil { | ||
assert.Equal(t, tt.wantComponents, d.Components, "The two values should be the same.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the existing assertion, with difference printed out. This will make debugging more difficult
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the assert module, other devfile repos are using it and the output is pretty clean and detailed and we dont have to write t.Errorf statements for assertion:
--- FAIL: TestDevfile200_AddVolumeMount/add_the_volume_mount_when_other_mounts_are_present (0.00s)
/Users/maysun/dev/devfile/parser/pkg/devfile/parser/data/v2/volumes_test.go:172:
Error Trace: volumes_test.go:172
Error: Not equal:
expected: []v1alpha2.Component{v1alpha2.Component{Name:"container0", Attributes:attributes.Attributes(nil), ComponentUnion:v1alpha2.ComponentUnion{ComponentType:"", Container:(*v1alpha2.ContainerComponent)(0xc0001a01e0), Kubernetes:(*v1alpha2.KubernetesComponent)(nil), Openshift:(*v1alpha2.OpenshiftComponent)(nil), Volume:(*v1alpha2.VolumeComponent)(nil), Plugin:(*v1alpha2.PluginComponent)(nil), Custom:(*v1alpha2.CustomComponent)(nil)}}, v1alpha2.Component{Name:"container1", Attributes:attributes.Attributes(nil), ComponentUnion:v1alpha2.ComponentUnion{ComponentType:"", Container:(*v1alpha2.ContainerComponent)(0xc0001a02d0), Kubernetes:(*v1alpha2.KubernetesComponent)(nil), Openshift:(*v1alpha2.OpenshiftComponent)(nil), Volume:(*v1alpha2.VolumeComponent)(nil), Plugin:(*v1alpha2.PluginComponent)(nil), Custom:(*v1alpha2.CustomComponent)(nil)}}}
actual : []v1alpha2.Component{v1alpha2.Component{Name:"container0", Attributes:attributes.Attributes(nil), ComponentUnion:v1alpha2.ComponentUnion{ComponentType:"", Container:(*v1alpha2.ContainerComponent)(0xc0001a0000), Kubernetes:(*v1alpha2.KubernetesComponent)(nil), Openshift:(*v1alpha2.OpenshiftComponent)(nil), Volume:(*v1alpha2.VolumeComponent)(nil), Plugin:(*v1alpha2.PluginComponent)(nil), Custom:(*v1alpha2.CustomComponent)(nil)}}, v1alpha2.Component{Name:"container1", Attributes:attributes.Attributes(nil), ComponentUnion:v1alpha2.ComponentUnion{ComponentType:"", Container:(*v1alpha2.ContainerComponent)(0xc0001a00f0), Kubernetes:(*v1alpha2.KubernetesComponent)(nil), Openshift:(*v1alpha2.OpenshiftComponent)(nil), Volume:(*v1alpha2.VolumeComponent)(nil), Plugin:(*v1alpha2.PluginComponent)(nil), Custom:(*v1alpha2.CustomComponent)(nil)}}}
Diff:
--- Expected
+++ Actual
@@ -15,3 +15,3 @@
Name: (string) (len=7) "volume1",
- Path: (string) (len=3) "/da"
+ Path: (string) (len=5) "/data"
},
Test: TestDevfile200_AddVolumeMount/add_the_volume_mount_when_other_mounts_are_present
Messages: The two values should be the same.
Also, can we add description in the PR's main description, stating that all deletion APIs are just doing basic deletion ,the returned devfileObj is not guaranteed to be valid? I saw volume mount APIs got changed a lot, probably also want to state those changes in the main description, so odo folks can easily get what have been changed; and what is expected now from those functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is introducing a bunch of breaking changes.
What is the versioning policy for this library?
How other projects using this library can know when it is safe to update the library and when it is not because there were breaking changes in the interface?
UPDATE:
That might sound maybe a little bit harsh. :-( I don't want to say that API can't change in a breaking way. But in order for people to use any library, there need to be clear version rules and ideally a changelog so consumers can know what changed when.
|
||
// command related methods | ||
GetCommands(common.DevfileOptions) ([]v1.Command, error) | ||
AddCommands(commands ...v1.Command) error | ||
AddCommands(commands []v1.Command) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is changing the signature of an existing function. It will break every project that already uses this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kadel Thanks for the review and concern. Yes, you're right, there should be a release every sprint, specifically when there is a breaking change with the changelog of the PR/commit.
Let me think about it a bit more. Since devfile/library has never had a release, I can do a v1.0.0-alpha release after this PR.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be great. At least we would know when and what problems we can expect when upgrading to a newer version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kadel do you have any other concerns before I merge this PR? I am looking to do a pre-release once this PR is in and hopefully generate a changelog for the release so its easier to track changes.
|
||
// volume related methods | ||
AddVolume(volume v1.Component, path string) error | ||
DeleteVolume(name string) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is removing existing functions that other tools might be already using. This is a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're right
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
Signed-off-by: Maysun J Faisal <[email protected]>
if d.Commands[i].Id == id { | ||
found = true | ||
d.Commands = append(d.Commands[:i], d.Commands[i+1:]...) | ||
break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually can just return nil
at this point. and the found
is not needed
same for the other delete functions
} | ||
|
||
if err == nil && !tt.wantErr { | ||
assert.Equal(t, tt.wantCommands, d.Commands, "The two values should be the same.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this if statement, already checked by the previous one (previous if block should return)
can directly check tt.wantCommands & d.Commands
same for the other tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no we cant, because there are tests where we want an err and that should be skipped. The prev if block is to check if there is an err mis match. I've updated it to a better if else block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it won't be skipped, because it's returning from that single test func(t *testing.T){}
, it will continue the for loop for the rest of tests
if !tt.wantErr && got != nil { | ||
t.Errorf("TestDevfile200_AddEvents() unexpected error - %+v", got) | ||
} else if tt.wantErr && got == nil { | ||
if !tt.wantErr && err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can combine this two checks into one if (err != nil) != tt.wantErr
tmp = append(tmp, volumeMount) | ||
for i := range d.Components { | ||
if d.Components[i].Container != nil && d.Components[i].Name != name { | ||
for j := len(d.Components[i].Container.VolumeMounts) - 1; j >= 0; j-- { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should have a comment here explains why we are doing a decremental for loop
for _, volumeMount := range component.Container.VolumeMounts { | ||
if volumeMount.Name == name { | ||
if volumeMount.Name == mountName { | ||
mountFound = true | ||
path = volumeMount.Path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
directly return if component & mount both found
Signed-off-by: Maysun J Faisal <[email protected]>
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: maysunfaisal, yangcao77 The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Signed-off-by: Maysun J Faisal [email protected]
What does this PR do?
Adds ability to delete other devfile objects. As a result few items have changed:
AddCommands(commands ...v1.Command) error
updated toAddCommands(commands []v1.Command) error
to be consistentAddVolume(volume v1.Component, path string) error
removedDeleteVolumeMount(name string) error
removedGetVolumeMountPath(name string) (string, error)
updated toGetVolumeMountPath(mountName, componentName string) (string, error)
since a container can have multiple volume mounts and returning the first volume mount with the name is not logically correctWhat issues does this PR fix or reference?
Fixes devfile/api#357
Fixes devfile/api#363
Is your PR tested? Consider putting some instruction how to test your changes
yes, new tests