-
Notifications
You must be signed in to change notification settings - Fork 244
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
Deploy, Events without devfile/adapters #5460
Merged
openshift-merge-robot
merged 12 commits into
redhat-developer:main
from
feloy:without-devfile-adapters
Feb 23, 2022
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
05656bd
Execute devfile command
feloy b2ce427
Undeploy
feloy ae7f800
cleanup devfile/adapters
feloy 9b06644
refactor
feloy a6b8f40
Move GetOnePod to component package
feloy 049a9f8
Move DoesComponentExist and Log from devfile/adapter to component pac…
feloy cd3a4ed
Exec without devfile/adapters
feloy d5ef2dd
Move Delete from devfile/adapters to component
feloy a140294
Remove old Deploy code
feloy 740b20f
review
feloy 669f5c1
Add tests for issue 5454
feloy 9f5923c
Review
feloy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package component | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" | ||
"github.com/pkg/errors" | ||
"github.com/redhat-developer/odo/pkg/kclient" | ||
"github.com/redhat-developer/odo/pkg/log" | ||
"github.com/redhat-developer/odo/pkg/machineoutput" | ||
"github.com/redhat-developer/odo/pkg/util" | ||
"k8s.io/klog" | ||
) | ||
|
||
type execHandler struct { | ||
kubeClient kclient.ClientInterface | ||
podName string | ||
show bool | ||
} | ||
|
||
const ShellExecutable string = "/bin/sh" | ||
|
||
func NewExecHandler(kubeClient kclient.ClientInterface, podName string, show bool) *execHandler { | ||
return &execHandler{ | ||
kubeClient: kubeClient, | ||
podName: podName, | ||
show: show, | ||
} | ||
} | ||
|
||
func (o *execHandler) ApplyImage(image v1alpha2.Component) error { | ||
return nil | ||
} | ||
|
||
func (o *execHandler) ApplyKubernetes(kubernetes v1alpha2.Component) error { | ||
return nil | ||
} | ||
|
||
func (o *execHandler) Execute(command v1alpha2.Command) error { | ||
msg := fmt.Sprintf("Executing %s command %q on container %q", command.Id, command.Exec.CommandLine, command.Exec.Component) | ||
spinner := log.Spinner(msg) | ||
defer spinner.End(false) | ||
|
||
logger := machineoutput.NewMachineEventLoggingClient() | ||
stdoutWriter, stdoutChannel, stderrWriter, stderrChannel := logger.CreateContainerOutputWriter() | ||
|
||
cmdline := getCmdline(command) | ||
err := executeCommand(o.kubeClient, command.Exec.Component, o.podName, cmdline, o.show, stdoutWriter, stderrWriter) | ||
|
||
closeWriterAndWaitForAck(stdoutWriter, stdoutChannel, stderrWriter, stderrChannel) | ||
|
||
spinner.End(true) | ||
return err | ||
} | ||
|
||
func getCmdline(command v1alpha2.Command) []string { | ||
// deal with environment variables | ||
var cmdLine string | ||
setEnvVariable := util.GetCommandStringFromEnvs(command.Exec.Env) | ||
|
||
if setEnvVariable == "" { | ||
cmdLine = command.Exec.CommandLine | ||
} else { | ||
cmdLine = setEnvVariable + " && " + command.Exec.CommandLine | ||
} | ||
|
||
// Change to the workdir and execute the command | ||
var cmd []string | ||
if command.Exec.WorkingDir != "" { | ||
// since we are using /bin/sh -c, the command needs to be within a single double quote instance, for example "cd /tmp && pwd" | ||
cmd = []string{ShellExecutable, "-c", "cd " + command.Exec.WorkingDir + " && " + cmdLine} | ||
} else { | ||
cmd = []string{ShellExecutable, "-c", cmdLine} | ||
} | ||
return cmd | ||
} | ||
|
||
func closeWriterAndWaitForAck(stdoutWriter *io.PipeWriter, stdoutChannel chan interface{}, stderrWriter *io.PipeWriter, stderrChannel chan interface{}) { | ||
if stdoutWriter != nil { | ||
_ = stdoutWriter.Close() | ||
<-stdoutChannel | ||
} | ||
if stderrWriter != nil { | ||
_ = stderrWriter.Close() | ||
<-stderrChannel | ||
} | ||
} | ||
|
||
// ExecuteCommand executes the given command in the pod's container | ||
func executeCommand(client kclient.ClientInterface, containerName string, podName string, command []string, show bool, consoleOutputStdout *io.PipeWriter, consoleOutputStderr *io.PipeWriter) (err error) { | ||
stdoutReader, stdoutWriter := io.Pipe() | ||
stderrReader, stderrWriter := io.Pipe() | ||
|
||
var cmdOutput string | ||
|
||
klog.V(2).Infof("Executing command %v for pod: %v in container: %v", command, podName, containerName) | ||
|
||
// Read stdout and stderr, store their output in cmdOutput, and also pass output to consoleOutput Writers (if non-nil) | ||
stdoutCompleteChannel := startReaderGoroutine(stdoutReader, show, &cmdOutput, consoleOutputStdout) | ||
stderrCompleteChannel := startReaderGoroutine(stderrReader, show, &cmdOutput, consoleOutputStderr) | ||
|
||
err = client.ExecCMDInContainer(containerName, podName, command, stdoutWriter, stderrWriter, nil, false) | ||
|
||
// Block until we have received all the container output from each stream | ||
_ = stdoutWriter.Close() | ||
<-stdoutCompleteChannel | ||
_ = stderrWriter.Close() | ||
<-stderrCompleteChannel | ||
|
||
if err != nil { | ||
// It is safe to read from cmdOutput here, as the goroutines are guaranteed to have terminated at this point. | ||
klog.V(2).Infof("ExecuteCommand returned an an err: %v. for command '%v'. output: %v", err, command, cmdOutput) | ||
|
||
return errors.Wrapf(err, "unable to exec command %v: \n%v", command, cmdOutput) | ||
} | ||
|
||
return | ||
} | ||
|
||
// This goroutine will automatically pipe the output from the writer (passed into ExecCMDInContainer) to | ||
// the loggers. | ||
// The returned channel will contain a single nil entry once the reader has closed. | ||
func startReaderGoroutine(reader io.Reader, show bool, cmdOutput *string, consoleOutput *io.PipeWriter) chan interface{} { | ||
|
||
result := make(chan interface{}) | ||
|
||
go func() { | ||
scanner := bufio.NewScanner(reader) | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
|
||
if log.IsDebug() || show { | ||
_, err := fmt.Fprintln(os.Stdout, line) | ||
if err != nil { | ||
log.Errorf("Unable to print to stdout: %s", err.Error()) | ||
} | ||
} | ||
|
||
*cmdOutput += fmt.Sprintln(line) | ||
|
||
if consoleOutput != nil { | ||
_, err := consoleOutput.Write([]byte(line + "\n")) | ||
if err != nil { | ||
log.Errorf("Error occurred on writing string to consoleOutput writer: %s", err.Error()) | ||
} | ||
} | ||
} | ||
result <- nil | ||
}() | ||
|
||
return result | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package deploy | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" | ||
"github.com/devfile/library/pkg/devfile/parser" | ||
devfilefs "github.com/devfile/library/pkg/testingutil/filesystem" | ||
|
||
"github.com/pkg/errors" | ||
|
||
componentlabels "github.com/redhat-developer/odo/pkg/component/labels" | ||
"github.com/redhat-developer/odo/pkg/devfile/image" | ||
"github.com/redhat-developer/odo/pkg/kclient" | ||
"github.com/redhat-developer/odo/pkg/libdevfile" | ||
"github.com/redhat-developer/odo/pkg/log" | ||
"github.com/redhat-developer/odo/pkg/service" | ||
) | ||
|
||
type DeployClient struct { | ||
kubeClient kclient.ClientInterface | ||
} | ||
|
||
func NewDeployClient(kubeClient kclient.ClientInterface) *DeployClient { | ||
return &DeployClient{ | ||
kubeClient: kubeClient, | ||
} | ||
} | ||
|
||
func (o *DeployClient) Deploy(devfileObj parser.DevfileObj, path string, appName string) error { | ||
deployHandler := newDeployHandler(devfileObj, path, o.kubeClient, appName) | ||
return libdevfile.Deploy(devfileObj, deployHandler) | ||
} | ||
|
||
type deployHandler struct { | ||
devfileObj parser.DevfileObj | ||
path string | ||
kubeClient kclient.ClientInterface | ||
appName string | ||
} | ||
|
||
func newDeployHandler(devfileObj parser.DevfileObj, path string, kubeClient kclient.ClientInterface, appName string) *deployHandler { | ||
return &deployHandler{ | ||
devfileObj: devfileObj, | ||
path: path, | ||
kubeClient: kubeClient, | ||
appName: appName, | ||
} | ||
} | ||
|
||
func (o *deployHandler) ApplyImage(img v1alpha2.Component) error { | ||
return image.BuildPushSpecificImage(o.devfileObj, o.path, img, true) | ||
} | ||
|
||
func (o *deployHandler) ApplyKubernetes(kubernetes v1alpha2.Component) error { | ||
// validate if the GVRs represented by Kubernetes inlined components are supported by the underlying cluster | ||
_, err := service.ValidateResourceExist(o.kubeClient, kubernetes, o.path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
labels := componentlabels.GetLabels(kubernetes.Name, o.appName, true) | ||
u, err := service.GetK8sComponentAsUnstructured(kubernetes.Kubernetes, o.path, devfilefs.DefaultFs{}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
log.Infof("\nDeploying Kubernetes %s: %s", u.GetKind(), u.GetName()) | ||
isOperatorBackedService, err := service.PushKubernetesResource(o.kubeClient, u, labels) | ||
if err != nil { | ||
return errors.Wrap(err, "failed to create service(s) associated with the component") | ||
} | ||
if isOperatorBackedService { | ||
log.Successf("Kubernetes resource %q on the cluster; refer %q to know how to link it to the component", strings.Join([]string{u.GetKind(), u.GetName()}, "/"), "odo link -h") | ||
|
||
} | ||
return nil | ||
} | ||
|
||
func (o *deployHandler) Execute(command v1alpha2.Command) error { | ||
return errors.New("Exec command is not implemented for Deploy") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package deploy | ||
|
||
import "github.com/devfile/library/pkg/devfile/parser" | ||
|
||
type Client interface { | ||
// Deploy resources from a devfile located in path, for the specified appName | ||
Deploy(devfileObj parser.DevfileObj, path string, appName string) error | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Among all the packages we have created the client interface for at business layer,
component
package has not been modified so far. Does it make sense to do it here?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.
The component one is the more complex to refactor. I'll need to sleep before ;) Joke aside, no, it will deserve PR all to itself
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'm going to need some sleep myself to form a mental picture of odo's code architecture. You're changing it fast (not complaining, complimenting), and it's tough to keep up at times. 😅