Skip to content
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

Moves project related code from occlient.go to a new file #4211

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 0 additions & 215 deletions pkg/occlient/occlient.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import (
buildv1 "github.com/openshift/api/build/v1"
dockerapiv10 "github.com/openshift/api/image/docker10"
imagev1 "github.com/openshift/api/image/v1"
projectv1 "github.com/openshift/api/project/v1"
routev1 "github.com/openshift/api/route/v1"
oauthv1client "github.com/openshift/client-go/oauth/clientset/versioned/typed/oauth/v1"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -455,116 +454,6 @@ func isServerUp(server string) bool {
return true
}

func (c *Client) GetCurrentProjectName() string {
return c.Namespace
}

// GetProjectNames return list of existing projects that user has access to.
func (c *Client) GetProjectNames() ([]string, error) {
projects, err := c.projectClient.Projects().List(metav1.ListOptions{})
if err != nil {
return nil, errors.Wrap(err, "unable to list projects")
}

var projectNames []string
for _, p := range projects.Items {
projectNames = append(projectNames, p.Name)
}
return projectNames, nil
}

// GetProject returns project based on the name of the project.Errors related to
// project not being found or forbidden are translated to nil project for compatibility
func (c *Client) GetProject(projectName string) (*projectv1.Project, error) {
prj, err := c.projectClient.Projects().Get(projectName, metav1.GetOptions{})
if err != nil {
istatus, ok := err.(kerrors.APIStatus)
if ok {
status := istatus.Status()
if status.Reason == metav1.StatusReasonNotFound || status.Reason == metav1.StatusReasonForbidden {
return nil, nil
}
} else {
return nil, err
}

}
return prj, err

}

// CreateNewProject creates project with given projectName
func (c *Client) CreateNewProject(projectName string, wait bool) error {
// Instantiate watcher before requesting new project
// If watched is created after the project it can lead to situation when the project is created before the watcher.
// When this happens, it gets stuck waiting for event that already happened.
var watcher watch.Interface
var err error
if wait {
watcher, err = c.projectClient.Projects().Watch(metav1.ListOptions{
FieldSelector: fields.Set{"metadata.name": projectName}.AsSelector().String(),
})
if err != nil {
return errors.Wrapf(err, "unable to watch new project %s creation", projectName)
}
defer watcher.Stop()
}

projectRequest := &projectv1.ProjectRequest{
ObjectMeta: metav1.ObjectMeta{
Name: projectName,
},
}
_, err = c.projectClient.ProjectRequests().Create(projectRequest)
if err != nil {
return errors.Wrapf(err, "unable to create new project %s", projectName)
}

if watcher != nil {
for {
val, ok := <-watcher.ResultChan()
if !ok {
break
}
if prj, ok := val.Object.(*projectv1.Project); ok {
klog.V(3).Infof("Status of creation of project %s is %s", prj.Name, prj.Status.Phase)
switch prj.Status.Phase {
//prj.Status.Phase can only be "Terminating" or "Active" or ""
case corev1.NamespaceActive:
if val.Type == watch.Added {
klog.V(3).Infof("Project %s now exists", prj.Name)
return nil
}
if val.Type == watch.Error {
return fmt.Errorf("failed watching the deletion of project %s", prj.Name)
}
}
}
}
}

return nil
}

// SetCurrentProject sets the given projectName to current project
func (c *Client) SetCurrentProject(projectName string) error {
rawConfig, err := c.KubeConfig.RawConfig()
if err != nil {
return errors.Wrapf(err, "unable to switch to %s project", projectName)
}

rawConfig.Contexts[rawConfig.CurrentContext].Namespace = projectName

err = clientcmd.ModifyConfig(clientcmd.NewDefaultClientConfigLoadingRules(), rawConfig, true)
if err != nil {
return errors.Wrapf(err, "unable to switch to %s project", projectName)
}

// we set the current namespace to the current project as well
c.Namespace = projectName
return nil
}

// addLabelsToArgs adds labels from map to args as a new argument in format that oc requires
// --labels label1=value1,label2=value2
func addLabelsToArgs(labels map[string]string, args []string) []string {
Expand Down Expand Up @@ -2181,105 +2070,6 @@ func (c *Client) DeleteServiceInstance(labels map[string]string) error {
return nil
}

// DeleteProject deletes given project
//
// NOTE:
// There is a very specific edge case that may happen during project deletion when deleting a project and then immediately creating another.
// Unfortunately, despite the watch interface, we cannot safely determine if the project is 100% deleted. See this link:
// https://stackoverflow.com/questions/48208001/deleted-openshift-online-pro-project-has-left-a-trace-so-cannot-create-project-o
// Will Gordon (Engineer @ Red Hat) describes the issue:
//
// "Projects are deleted asynchronously after you send the delete command. So it's possible that the deletion just hasn't been reconciled yet. It should happen within a minute or so, so try again.
// Also, please be aware that in a multitenant environment, like OpenShift Online, you are prevented from creating a project with the same name as any other project in the cluster, even if it's not your own. So if you can't create the project, it's possible that someone has already created a project with the same name."
func (c *Client) DeleteProject(name string, wait bool) error {

// Instantiate watcher for our "wait" function
var watcher watch.Interface
var err error

// If --wait has been passed, we will wait for the project to fully be deleted
if wait {
watcher, err = c.projectClient.Projects().Watch(metav1.ListOptions{
FieldSelector: fields.Set{"metadata.name": name}.AsSelector().String(),
})
if err != nil {
return errors.Wrapf(err, "unable to watch project")
}
defer watcher.Stop()
}

// Delete the project
err = c.projectClient.Projects().Delete(name, &metav1.DeleteOptions{})
if err != nil {
return errors.Wrap(err, "unable to delete project")
}

// If watcher has been created (wait was passed) we will create a go routine and actually **wait**
// until *EVERYTHING* is successfully deleted.
if watcher != nil {

// Project channel
// Watch error channel
projectChannel := make(chan *projectv1.Project)
watchErrorChannel := make(chan error)

// Create a go routine to run in the background
go func() {

for {

// If watch unexpected has been closed..
val, ok := <-watcher.ResultChan()
if !ok {
//return fmt.Errorf("received unexpected signal %+v on project watch channel", val)
watchErrorChannel <- errors.Errorf("watch channel was closed unexpectedly: %+v", val)
break
}

// So we depend on val.Type as val.Object.Status.Phase is just empty string and not a mapped value constant
if projectStatus, ok := val.Object.(*projectv1.Project); ok {

klog.V(3).Infof("Status of delete of project %s is '%s'", name, projectStatus.Status.Phase)

switch projectStatus.Status.Phase {
//projectStatus.Status.Phase can only be "Terminating" or "Active" or ""
case "":
if val.Type == watch.Deleted {
projectChannel <- projectStatus
break
}
if val.Type == watch.Error {
watchErrorChannel <- errors.Errorf("failed watching the deletion of project %s", name)
break
}
}

} else {
watchErrorChannel <- errors.New("unable to convert event object to Project")
break
}

}
close(projectChannel)
close(watchErrorChannel)
}()

select {
case val := <-projectChannel:
klog.V(3).Infof("Deletion information for project: %+v", val)
return nil
case err := <-watchErrorChannel:
return err
case <-time.After(waitForProjectDeletionTimeOut):
return errors.Errorf("waited %s but couldn't delete project %s in time", waitForProjectDeletionTimeOut, name)
}

}

// Return nil since we don't bother checking for the watcher..
return nil
}

// GetDeploymentConfigLabelValues get label values of given label from objects in project that are matching selector
// returns slice of unique label values
func (c *Client) GetDeploymentConfigLabelValues(label string, selector string) ([]string, error) {
Expand Down Expand Up @@ -3275,11 +3065,6 @@ func (c *Client) IsRouteSupported() (bool, error) {
return c.isResourceSupported("route.openshift.io", "v1", "routes")
}

// IsProjectSupported checks if Project resource type is present on the cluster
func (c *Client) IsProjectSupported() (bool, error) {
return c.isResourceSupported("project.openshift.io", "v1", "projects")
}

// IsImageStreamSupported checks if imagestream resource type is present on the cluster
func (c *Client) IsImageStreamSupported() (bool, error) {

Expand Down
94 changes: 0 additions & 94 deletions pkg/occlient/occlient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
dockerapi "github.com/openshift/api/image/docker10"
dockerapiv10 "github.com/openshift/api/image/docker10"
imagev1 "github.com/openshift/api/image/v1"
projectv1 "github.com/openshift/api/project/v1"
routev1 "github.com/openshift/api/route/v1"
applabels "github.com/openshift/odo/pkg/application/labels"
componentlabels "github.com/openshift/odo/pkg/component/labels"
Expand Down Expand Up @@ -2525,99 +2524,6 @@ func TestWaitAndGetSecret(t *testing.T) {
}
}

func TestCreateNewProject(t *testing.T) {
tests := []struct {
name string
projName string
wait bool
wantErr bool
}{
{
name: "Case 1: valid project name, not waiting",
projName: "testing",
wait: false,
wantErr: false,
},
{
name: "Case 2: valid project name, waiting",
projName: "testing2",
wait: true,
wantErr: false,
},
// {
// name: "Case 2: empty project name",
// projName: "",
// wantErr: true,
// },
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fkclient, fkclientset := FakeNew()

fkclientset.ProjClientset.PrependReactor("create", "projectrequests", func(action ktesting.Action) (bool, runtime.Object, error) {
proj := projectv1.Project{
ObjectMeta: metav1.ObjectMeta{
Name: tt.projName,
},
}
return true, &proj, nil
})

if tt.wait {
fkWatch := watch.NewFake()
// Change the status
go func() {
fkWatch.Add(&projectv1.Project{
ObjectMeta: metav1.ObjectMeta{
Name: tt.projName,
},
Status: projectv1.ProjectStatus{Phase: "Active"},
})
}()
fkclientset.ProjClientset.PrependWatchReactor("projects", func(action ktesting.Action) (handled bool, ret watch.Interface, err error) {
if len(tt.projName) == 0 {
return true, nil, fmt.Errorf("error watching project")
}
return true, fkWatch, nil
})
}

err := fkclient.CreateNewProject(tt.projName, tt.wait)
if !tt.wantErr == (err != nil) {
t.Errorf("client.CreateNewProject(string) unexpected error %v, wantErr %v", err, tt.wantErr)
}

actions := fkclientset.ProjClientset.Actions()
actionsLen := len(actions)
if !tt.wait && actionsLen != 1 {
t.Errorf("expected 1 action in CreateNewProject got: %v", actions)
}
if tt.wait && actionsLen != 2 {
t.Errorf("expected 2 actions in CreateNewProject when waiting for project creation got: %v", actions)
}

if err == nil {
createdProj := actions[actionsLen-1].(ktesting.CreateAction).GetObject().(*projectv1.ProjectRequest)

if createdProj.Name != tt.projName {
t.Errorf("project name does not match the expected name, expected: %s, got: %s", tt.projName, createdProj.Name)
}

if tt.wait {
expectedFields := fields.OneTermEqualSelector("metadata.name", tt.projName)
gotFields := actions[0].(ktesting.WatchAction).GetWatchRestrictions().Fields

if !reflect.DeepEqual(expectedFields, gotFields) {
t.Errorf("Fields not matching: expected: %s, got %s", expectedFields, gotFields)
}
}
}

})
}
}

func TestCreateService(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading