mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
Add odo logs (#5760)
* Add odo logs * Nolint for random number generation * Changes based on Philippe's PR review * Add logs for `odo logs` * Add nolint at the right place to fix unit tests * Changes based on PR feedback * Name the key in unstructured.Unstructured * Name containers with same names as c, c1, c2 * Remove unused struct field * Modify documentation to follow general pattern * Undo the changes done in earlier commits * odo logs help message is accurate * Update docs/website/versioned_docs/version-3.0.0/command-reference/logs.md Co-authored-by: Parthvi Vala <pvala@redhat.com> * Fixes broken link rendering * Correct the example used in odo logs doc * Make container name clearer in odo logs output * Wrap at 120 chars, not 80 * Fixes to the document after rebase mistake Co-authored-by: Parthvi Vala <pvala@redhat.com>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
---
|
||||
title: odo logs
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
`odo logs` is used to display the logs for all the containers odo created for the component under current working
|
||||
directory.
|
||||
|
||||
## Running the command
|
||||
|
||||
If you haven't already done so, you must [initialize](../command-reference/init) your source code with the `odo
|
||||
init` command. Next, run the `odo dev` command so that odo can create the resources on the Kubernetes cluster.
|
||||
|
||||
Consider a devfile.yaml like below which was used to create inner loop resources using `odo dev`. Notice that
|
||||
multiple containers have been named as `main` to show how `odo logs` would display logs when more than one
|
||||
containers have the same name:
|
||||
```yaml
|
||||
metadata:
|
||||
description: Stack with Node.js 14
|
||||
displayName: Node.js Runtime
|
||||
icon: https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg
|
||||
language: javascript
|
||||
name: node
|
||||
projectType: nodejs
|
||||
tags:
|
||||
- NodeJS
|
||||
- Express
|
||||
- ubi8
|
||||
version: 1.0.1
|
||||
schemaVersion: 2.0.0
|
||||
starterProjects:
|
||||
- git:
|
||||
remotes:
|
||||
origin: https://github.com/odo-devfiles/nodejs-ex.git
|
||||
name: nodejs-starter
|
||||
commands:
|
||||
- exec:
|
||||
commandLine: npm install
|
||||
component: runtime
|
||||
group:
|
||||
isDefault: true
|
||||
kind: build
|
||||
workingDir: ${PROJECT_SOURCE}
|
||||
id: install
|
||||
- exec:
|
||||
commandLine: npm start
|
||||
component: runtime
|
||||
group:
|
||||
isDefault: true
|
||||
kind: run
|
||||
workingDir: ${PROJECT_SOURCE}
|
||||
id: run
|
||||
- exec:
|
||||
commandLine: npm run debug
|
||||
component: runtime
|
||||
group:
|
||||
isDefault: true
|
||||
kind: debug
|
||||
workingDir: ${PROJECT_SOURCE}
|
||||
id: debug
|
||||
- exec:
|
||||
commandLine: npm test
|
||||
component: runtime
|
||||
group:
|
||||
isDefault: true
|
||||
kind: test
|
||||
workingDir: ${PROJECT_SOURCE}
|
||||
id: test
|
||||
components:
|
||||
- container:
|
||||
endpoints:
|
||||
- name: http-3000
|
||||
targetPort: 3000
|
||||
image: registry.access.redhat.com/ubi8/nodejs-14:latest
|
||||
memoryLimit: 1024Mi
|
||||
mountSources: true
|
||||
name: runtime
|
||||
- name: infinitepodone
|
||||
kubernetes:
|
||||
inlined: |
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: infinitepodone
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: docker.io/dharmit/infiniteloop
|
||||
- name: infinitedeployment
|
||||
kubernetes:
|
||||
inlined: |
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: infinitedeployment
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: infinite
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: infinite
|
||||
spec:
|
||||
containers:
|
||||
- name: main
|
||||
image: docker.io/dharmit/infiniteloop
|
||||
```
|
||||
When you do `odo dev`, odo creates pods for:
|
||||
1. The component named `node` itself. Containers for this are created using `.components.container`.
|
||||
2. Kubernetes component named `infinitepodone`
|
||||
3. Kubernetes component named `infinitedeployment`. As can be seen under `.spec.template.spec.containers` for this
|
||||
particular component, it creates one container for it.
|
||||
|
||||
When you run `odo logs`, you should see logs from all these containers. Each line is prefixed with
|
||||
`<container-name>:` to easily distinguish which the container the logs belong to. Since we named multiple
|
||||
containers in the `devfile.yaml` as `main`, `odo logs` has distinguished these containers as `main` and `main[1]`:
|
||||
```shell
|
||||
$ odo logs
|
||||
main: Fri May 27 06:17:30 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:31 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:32 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:33 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:34 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:35 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:36 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:37 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:38 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:39 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:40 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:41 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:42 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:44 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:45 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:46 UTC 2022 - this is infinite for loop
|
||||
main: Fri May 27 06:17:47 UTC 2022 - this is infinite for loop
|
||||
runtime: time="2022-05-27T06:17:36Z" level=info msg="create process:devrun"
|
||||
runtime: time="2022-05-27T06:17:36Z" level=info msg="create process:debugrun"
|
||||
runtime: time="2022-05-27T06:17:36Z" level=info msg="try to start program" program=devrun
|
||||
runtime: time="2022-05-27T06:17:36Z" level=info msg="success to start program" program=devrun
|
||||
runtime: time="2022-05-27T06:17:37Z" level=debug msg="no auth required"
|
||||
runtime: time="2022-05-27T06:17:37Z" level=debug msg="wait program exit" program=devrun
|
||||
runtime: time="2022-05-27T06:17:37Z" level=info msg="program stopped with status:exit status 0" program=devrun
|
||||
runtime: time="2022-05-27T06:17:37Z" level=info msg="Don't start the stopped program because its autorestart flag is false" program=devrun
|
||||
runtime: time="2022-05-27T06:17:41Z" level=debug msg="no auth required"
|
||||
runtime: time="2022-05-27T06:17:41Z" level=debug msg="succeed to find process:devrun"
|
||||
runtime: time="2022-05-27T06:17:41Z" level=info msg="try to start program" program=devrun
|
||||
runtime: time="2022-05-27T06:17:41Z" level=info msg="success to start program" program=devrun
|
||||
runtime: ODO_COMMAND_RUN is npm start
|
||||
runtime: Changing directory to ${PROJECT_SOURCE}
|
||||
runtime: Executing command cd ${PROJECT_SOURCE} && npm start
|
||||
runtime:
|
||||
runtime: > nodejs-starter@1.0.0 start /projects
|
||||
runtime: > node server.js
|
||||
runtime:
|
||||
runtime: App started on PORT 3000
|
||||
runtime: time="2022-05-27T06:17:42Z" level=debug msg="wait program exit" program=devrun
|
||||
runtime: time="2022-05-27T06:17:43Z" level=debug msg="no auth required"
|
||||
main1: Fri May 27 06:17:34 UTC 2022 - this is infinite for loop
|
||||
main1: Fri May 27 06:17:35 UTC 2022 - this is infinite for loop
|
||||
main1: Fri May 27 06:17:36 UTC 2022 - this is infinite for loop
|
||||
main1: Fri May 27 06:17:37 UTC 2022 - this is infinite for loop
|
||||
main1: Fri May 27 06:17:38 UTC 2022 - this is infinite for loop
|
||||
main1: Fri May 27 06:17:39 UTC 2022 - this is infinite for loop
|
||||
main1: Fri May 27 06:17:40 UTC 2022 - this is infinite for loop
|
||||
```
|
||||
@@ -5,155 +5,38 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
parser "github.com/devfile/library/pkg/devfile/parser"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
api "github.com/redhat-developer/odo/pkg/api"
|
||||
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockClient is a mock of Client interface
|
||||
// MockClient is a mock of Client interface.
|
||||
type MockClient struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockClientMockRecorder
|
||||
}
|
||||
|
||||
// MockClientMockRecorder is the mock recorder for MockClient
|
||||
// MockClientMockRecorder is the mock recorder for MockClient.
|
||||
type MockClientMockRecorder struct {
|
||||
mock *MockClient
|
||||
}
|
||||
|
||||
// NewMockClient creates a new mock instance
|
||||
// NewMockClient creates a new mock instance.
|
||||
func NewMockClient(ctrl *gomock.Controller) *MockClient {
|
||||
mock := &MockClient{ctrl: ctrl}
|
||||
mock.recorder = &MockClientMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetFlags mocks base method
|
||||
func (m *MockClient) GetFlags(flags map[string]string) map[string]string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetFlags", flags)
|
||||
ret0, _ := ret[0].(map[string]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetFlags indicates an expected call of GetFlags
|
||||
func (mr *MockClientMockRecorder) GetFlags(flags interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFlags", reflect.TypeOf((*MockClient)(nil).GetFlags), flags)
|
||||
}
|
||||
|
||||
// GetServiceInstances mocks base method
|
||||
func (m *MockClient) GetServiceInstances() (map[string]unstructured.Unstructured, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetServiceInstances")
|
||||
ret0, _ := ret[0].(map[string]unstructured.Unstructured)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetServiceInstances indicates an expected call of GetServiceInstances
|
||||
func (mr *MockClientMockRecorder) GetServiceInstances() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceInstances", reflect.TypeOf((*MockClient)(nil).GetServiceInstances))
|
||||
}
|
||||
|
||||
// GetBindingsFromDevfile mocks base method
|
||||
func (m *MockClient) GetBindingsFromDevfile(devfileObj parser.DevfileObj, context string) ([]api.ServiceBinding, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBindingsFromDevfile", devfileObj, context)
|
||||
ret0, _ := ret[0].([]api.ServiceBinding)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBindingsFromDevfile indicates an expected call of GetBindingsFromDevfile
|
||||
func (mr *MockClientMockRecorder) GetBindingsFromDevfile(devfileObj, context interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBindingsFromDevfile", reflect.TypeOf((*MockClient)(nil).GetBindingsFromDevfile), devfileObj, context)
|
||||
}
|
||||
|
||||
// GetBindingFromCluster mocks base method
|
||||
func (m *MockClient) GetBindingFromCluster(name string) (api.ServiceBinding, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBindingFromCluster", name)
|
||||
ret0, _ := ret[0].(api.ServiceBinding)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBindingFromCluster indicates an expected call of GetBindingFromCluster
|
||||
func (mr *MockClientMockRecorder) GetBindingFromCluster(name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBindingFromCluster", reflect.TypeOf((*MockClient)(nil).GetBindingFromCluster), name)
|
||||
}
|
||||
|
||||
// ValidateAddBinding mocks base method
|
||||
func (m *MockClient) ValidateAddBinding(flags map[string]string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ValidateAddBinding", flags)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ValidateAddBinding indicates an expected call of ValidateAddBinding
|
||||
func (mr *MockClientMockRecorder) ValidateAddBinding(flags interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAddBinding", reflect.TypeOf((*MockClient)(nil).ValidateAddBinding), flags)
|
||||
}
|
||||
|
||||
// SelectServiceInstance mocks base method
|
||||
func (m *MockClient) SelectServiceInstance(flags map[string]string, serviceMap map[string]unstructured.Unstructured) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SelectServiceInstance", flags, serviceMap)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SelectServiceInstance indicates an expected call of SelectServiceInstance
|
||||
func (mr *MockClientMockRecorder) SelectServiceInstance(flags, serviceMap interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectServiceInstance", reflect.TypeOf((*MockClient)(nil).SelectServiceInstance), flags, serviceMap)
|
||||
}
|
||||
|
||||
// AskBindingName mocks base method
|
||||
func (m *MockClient) AskBindingName(serviceName, componentName string, flags map[string]string) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AskBindingName", serviceName, componentName, flags)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AskBindingName indicates an expected call of AskBindingName
|
||||
func (mr *MockClientMockRecorder) AskBindingName(serviceName, componentName, flags interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskBindingName", reflect.TypeOf((*MockClient)(nil).AskBindingName), serviceName, componentName, flags)
|
||||
}
|
||||
|
||||
// AskBindAsFiles mocks base method
|
||||
func (m *MockClient) AskBindAsFiles(flags map[string]string) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AskBindAsFiles", flags)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AskBindAsFiles indicates an expected call of AskBindAsFiles
|
||||
func (mr *MockClientMockRecorder) AskBindAsFiles(flags interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskBindAsFiles", reflect.TypeOf((*MockClient)(nil).AskBindAsFiles), flags)
|
||||
}
|
||||
|
||||
// AddBinding mocks base method
|
||||
// AddBinding mocks base method.
|
||||
func (m *MockClient) AddBinding(bindingName string, bindAsFiles bool, unstructuredService unstructured.Unstructured, obj parser.DevfileObj) (parser.DevfileObj, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddBinding", bindingName, bindAsFiles, unstructuredService, obj)
|
||||
@@ -162,27 +45,102 @@ func (m *MockClient) AddBinding(bindingName string, bindAsFiles bool, unstructur
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AddBinding indicates an expected call of AddBinding
|
||||
// AddBinding indicates an expected call of AddBinding.
|
||||
func (mr *MockClientMockRecorder) AddBinding(bindingName, bindAsFiles, unstructuredService, obj interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBinding", reflect.TypeOf((*MockClient)(nil).AddBinding), bindingName, bindAsFiles, unstructuredService, obj)
|
||||
}
|
||||
|
||||
// ValidateRemoveBinding mocks base method
|
||||
func (m *MockClient) ValidateRemoveBinding(flags map[string]string) error {
|
||||
// AskBindAsFiles mocks base method.
|
||||
func (m *MockClient) AskBindAsFiles(flags map[string]string) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ValidateRemoveBinding", flags)
|
||||
ret0, _ := ret[0].(error)
|
||||
ret := m.ctrl.Call(m, "AskBindAsFiles", flags)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AskBindAsFiles indicates an expected call of AskBindAsFiles.
|
||||
func (mr *MockClientMockRecorder) AskBindAsFiles(flags interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskBindAsFiles", reflect.TypeOf((*MockClient)(nil).AskBindAsFiles), flags)
|
||||
}
|
||||
|
||||
// AskBindingName mocks base method.
|
||||
func (m *MockClient) AskBindingName(serviceName, componentName string, flags map[string]string) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AskBindingName", serviceName, componentName, flags)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AskBindingName indicates an expected call of AskBindingName.
|
||||
func (mr *MockClientMockRecorder) AskBindingName(serviceName, componentName, flags interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskBindingName", reflect.TypeOf((*MockClient)(nil).AskBindingName), serviceName, componentName, flags)
|
||||
}
|
||||
|
||||
// GetBindingFromCluster mocks base method.
|
||||
func (m *MockClient) GetBindingFromCluster(name string) (api.ServiceBinding, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBindingFromCluster", name)
|
||||
ret0, _ := ret[0].(api.ServiceBinding)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBindingFromCluster indicates an expected call of GetBindingFromCluster.
|
||||
func (mr *MockClientMockRecorder) GetBindingFromCluster(name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBindingFromCluster", reflect.TypeOf((*MockClient)(nil).GetBindingFromCluster), name)
|
||||
}
|
||||
|
||||
// GetBindingsFromDevfile mocks base method.
|
||||
func (m *MockClient) GetBindingsFromDevfile(devfileObj parser.DevfileObj, context string) ([]api.ServiceBinding, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBindingsFromDevfile", devfileObj, context)
|
||||
ret0, _ := ret[0].([]api.ServiceBinding)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBindingsFromDevfile indicates an expected call of GetBindingsFromDevfile.
|
||||
func (mr *MockClientMockRecorder) GetBindingsFromDevfile(devfileObj, context interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBindingsFromDevfile", reflect.TypeOf((*MockClient)(nil).GetBindingsFromDevfile), devfileObj, context)
|
||||
}
|
||||
|
||||
// GetFlags mocks base method.
|
||||
func (m *MockClient) GetFlags(flags map[string]string) map[string]string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetFlags", flags)
|
||||
ret0, _ := ret[0].(map[string]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ValidateRemoveBinding indicates an expected call of ValidateRemoveBinding
|
||||
func (mr *MockClientMockRecorder) ValidateRemoveBinding(flags interface{}) *gomock.Call {
|
||||
// GetFlags indicates an expected call of GetFlags.
|
||||
func (mr *MockClientMockRecorder) GetFlags(flags interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRemoveBinding", reflect.TypeOf((*MockClient)(nil).ValidateRemoveBinding), flags)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFlags", reflect.TypeOf((*MockClient)(nil).GetFlags), flags)
|
||||
}
|
||||
|
||||
// RemoveBinding mocks base method
|
||||
// GetServiceInstances mocks base method.
|
||||
func (m *MockClient) GetServiceInstances() (map[string]unstructured.Unstructured, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetServiceInstances")
|
||||
ret0, _ := ret[0].(map[string]unstructured.Unstructured)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetServiceInstances indicates an expected call of GetServiceInstances.
|
||||
func (mr *MockClientMockRecorder) GetServiceInstances() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceInstances", reflect.TypeOf((*MockClient)(nil).GetServiceInstances))
|
||||
}
|
||||
|
||||
// RemoveBinding mocks base method.
|
||||
func (m *MockClient) RemoveBinding(bindingName string, obj parser.DevfileObj) (parser.DevfileObj, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RemoveBinding", bindingName, obj)
|
||||
@@ -191,8 +149,51 @@ func (m *MockClient) RemoveBinding(bindingName string, obj parser.DevfileObj) (p
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// RemoveBinding indicates an expected call of RemoveBinding
|
||||
// RemoveBinding indicates an expected call of RemoveBinding.
|
||||
func (mr *MockClientMockRecorder) RemoveBinding(bindingName, obj interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveBinding", reflect.TypeOf((*MockClient)(nil).RemoveBinding), bindingName, obj)
|
||||
}
|
||||
|
||||
// SelectServiceInstance mocks base method.
|
||||
func (m *MockClient) SelectServiceInstance(flags map[string]string, serviceMap map[string]unstructured.Unstructured) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SelectServiceInstance", flags, serviceMap)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SelectServiceInstance indicates an expected call of SelectServiceInstance.
|
||||
func (mr *MockClientMockRecorder) SelectServiceInstance(flags, serviceMap interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectServiceInstance", reflect.TypeOf((*MockClient)(nil).SelectServiceInstance), flags, serviceMap)
|
||||
}
|
||||
|
||||
// ValidateAddBinding mocks base method.
|
||||
func (m *MockClient) ValidateAddBinding(flags map[string]string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ValidateAddBinding", flags)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ValidateAddBinding indicates an expected call of ValidateAddBinding.
|
||||
func (mr *MockClientMockRecorder) ValidateAddBinding(flags interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAddBinding", reflect.TypeOf((*MockClient)(nil).ValidateAddBinding), flags)
|
||||
}
|
||||
|
||||
// ValidateRemoveBinding mocks base method.
|
||||
func (m *MockClient) ValidateRemoveBinding(flags map[string]string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ValidateRemoveBinding", flags)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ValidateRemoveBinding indicates an expected call of ValidateRemoveBinding.
|
||||
func (mr *MockClientMockRecorder) ValidateRemoveBinding(flags interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRemoveBinding", reflect.TypeOf((*MockClient)(nil).ValidateRemoveBinding), flags)
|
||||
}
|
||||
|
||||
@@ -124,9 +124,6 @@ func Log(client kclient.ClientInterface, componentName string, appName string, f
|
||||
// We then return a list of "components" intended for listing / output purposes specifically for commands such as:
|
||||
// `odo list`
|
||||
// that are both odo and non-odo components.
|
||||
//
|
||||
// We then return a list of "components" intended for listing / output purposes specifically for commands such as:
|
||||
// `odo list`
|
||||
func ListAllClusterComponents(client kclient.ClientInterface, namespace string) ([]api.ComponentAbstract, error) {
|
||||
|
||||
// Get all the dynamic resources available
|
||||
@@ -155,8 +152,8 @@ func ListAllClusterComponents(client kclient.ClientInterface, namespace string)
|
||||
}
|
||||
|
||||
// Figure out the correct name to use
|
||||
// if there is no instance label, we SKIP the resource as
|
||||
// it is not a component essential for Kubernetes.
|
||||
// if there is no instance label (app.kubernetes.io/instance),
|
||||
// we SKIP the resource as it is not a component essential for Kubernetes.
|
||||
name := odolabels.GetComponentName(labels)
|
||||
if name == "" {
|
||||
continue
|
||||
|
||||
@@ -54,7 +54,7 @@ func (c *Client) GetBindableKinds() (bindingApi.BindableKinds, error) {
|
||||
return bindableKind, nil
|
||||
}
|
||||
|
||||
// GetBindableKindStatusRestMapping retuns a list of *meta.RESTMapping of all the bindable kind operator CRD
|
||||
// GetBindableKindStatusRestMapping returns a list of *meta.RESTMapping of all the bindable kind operator CRD
|
||||
func (c Client) GetBindableKindStatusRestMapping(bindableKindStatuses []bindingApi.BindableKindsStatus) ([]*meta.RESTMapping, error) {
|
||||
var result []*meta.RESTMapping
|
||||
for _, bks := range bindableKindStatuses {
|
||||
|
||||
@@ -29,6 +29,14 @@ type ClientInterface interface {
|
||||
// GetAllResourcesFromSelector returns all resources of any kind (including CRs) matching the given label selector
|
||||
GetAllResourcesFromSelector(selector string, ns string) ([]unstructured.Unstructured, error)
|
||||
|
||||
// binding.go
|
||||
IsServiceBindingSupported() (bool, error)
|
||||
GetBindableKinds() (bindingApi.BindableKinds, error)
|
||||
GetBindableKindStatusRestMapping(bindableKindStatuses []bindingApi.BindableKindsStatus) ([]*meta.RESTMapping, error)
|
||||
GetBindingServiceBinding(name string) (bindingApi.ServiceBinding, error)
|
||||
GetSpecServiceBinding(name string) (specApi.ServiceBinding, error)
|
||||
NewServiceBindingServiceObject(unstructuredService unstructured.Unstructured, bindingName string) (bindingApi.Service, error)
|
||||
|
||||
// deployment.go
|
||||
GetDeploymentByName(name string) (*appsv1.Deployment, error)
|
||||
GetOneDeployment(componentName, appName string) (*appsv1.Deployment, error)
|
||||
@@ -87,14 +95,6 @@ type ClientInterface interface {
|
||||
GetOperatorGVRList() ([]meta.RESTMapping, error)
|
||||
ConvertUnstructuredToResource(u unstructured.Unstructured, obj interface{}) error
|
||||
|
||||
// binding.go
|
||||
IsServiceBindingSupported() (bool, error)
|
||||
GetBindableKinds() (bindingApi.BindableKinds, error)
|
||||
GetBindableKindStatusRestMapping(bindableKindStatuses []bindingApi.BindableKindsStatus) ([]*meta.RESTMapping, error)
|
||||
GetBindingServiceBinding(name string) (bindingApi.ServiceBinding, error)
|
||||
GetSpecServiceBinding(name string) (specApi.ServiceBinding, error)
|
||||
NewServiceBindingServiceObject(unstructuredService unstructured.Unstructured, bindingName string) (bindingApi.Service, error)
|
||||
|
||||
// owner_reference.go
|
||||
TryWithBlockOwnerDeletion(ownerReference metav1.OwnerReference, exec func(ownerReference metav1.OwnerReference) error) error
|
||||
|
||||
@@ -105,6 +105,7 @@ type ClientInterface interface {
|
||||
GetPodUsingComponentName(componentName string) (*corev1.Pod, error)
|
||||
GetOnePodFromSelector(selector string) (*corev1.Pod, error)
|
||||
GetPodLogs(podName, containerName string, followLog bool) (io.ReadCloser, error)
|
||||
GetAllPodsInNamespace() (*corev1.PodList, error)
|
||||
|
||||
// port_forwarding.go
|
||||
// SetupPortForwarding creates port-forwarding for the pod on the port pairs provided in the
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -242,3 +242,7 @@ func (c *Client) GetPodLogs(podName, containerName string, followLog bool) (io.R
|
||||
|
||||
return rd, err
|
||||
}
|
||||
|
||||
func (c *Client) GetAllPodsInNamespace() (*corev1.PodList, error) {
|
||||
return c.KubeClient.CoreV1().Pods(c.Namespace).List(context.TODO(), metav1.ListOptions{})
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ const suffixSpacing = " "
|
||||
const prefixSpacing = " "
|
||||
|
||||
var mu sync.Mutex
|
||||
var colors = []color.Attribute{color.FgRed, color.FgGreen, color.FgYellow, color.FgBlue, color.FgMagenta, color.FgCyan, color.FgWhite}
|
||||
var colorCounter = 0
|
||||
|
||||
// Status is used to track ongoing status in a CLI, with a nice loading spinner
|
||||
// when attached to a terminal
|
||||
@@ -517,3 +519,10 @@ func getSpacingString() string {
|
||||
}
|
||||
return "•"
|
||||
}
|
||||
|
||||
// ColorPicker picks a color from colors slice defined at the starting of this file
|
||||
// It increments the colorCounter variable so that next iteration returns a different color
|
||||
func ColorPicker() color.Attribute {
|
||||
colorCounter++
|
||||
return colors[(colorCounter)%len(colors)]
|
||||
}
|
||||
|
||||
9
pkg/logs/interface.go
Normal file
9
pkg/logs/interface.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package logs
|
||||
|
||||
import "io"
|
||||
|
||||
type Client interface {
|
||||
// DevModeLogs gets logs for the provided component name and namespace. A component could have multiple pods and
|
||||
// containers running on the cluster. It returns a slice of maps where container name is the key and its logs are the value
|
||||
DevModeLogs(componentName string, namespace string) ([]map[string]io.ReadCloser, error)
|
||||
}
|
||||
108
pkg/logs/logs.go
Normal file
108
pkg/logs/logs.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/kclient"
|
||||
odolabels "github.com/redhat-developer/odo/pkg/labels"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
type LogsClient struct {
|
||||
kubernetesClient kclient.ClientInterface
|
||||
}
|
||||
|
||||
func NewLogsClient(kubernetesClient kclient.ClientInterface) *LogsClient {
|
||||
return &LogsClient{
|
||||
kubernetesClient: kubernetesClient,
|
||||
}
|
||||
}
|
||||
|
||||
var _ Client = (*LogsClient)(nil)
|
||||
|
||||
func (o *LogsClient) DevModeLogs(componentName string, namespace string) ([]map[string]io.ReadCloser, error) {
|
||||
// get all resources in the namespace which are running in Dev mode
|
||||
selector := odolabels.Builder().WithComponentName(componentName).WithMode(odolabels.ComponentDevMode).Selector()
|
||||
resources, err := o.kubernetesClient.GetAllResourcesFromSelector(selector, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get all pods in the namespace
|
||||
podList, err := o.kubernetesClient.GetAllPodsInNamespace()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// match pod ownerReference (if any) with resources running in Dev mode
|
||||
var pods []corev1.Pod
|
||||
for _, pod := range podList.Items {
|
||||
for _, owner := range pod.GetOwnerReferences() {
|
||||
match, err := o.matchOwnerReferenceWithResources(owner, resources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if match {
|
||||
pods = append(pods, pod)
|
||||
break // because we don't need to check other owner references of the pod anymore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get all containers from the pods of interest
|
||||
podContainersMap := map[string][]corev1.Container{}
|
||||
for _, pod := range pods {
|
||||
for _, container := range pod.Spec.Containers {
|
||||
if _, ok := podContainersMap[pod.Name]; !ok {
|
||||
podContainersMap[pod.Name] = []corev1.Container{container}
|
||||
} else {
|
||||
podContainersMap[pod.Name] = append(podContainersMap[pod.Name], container)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get logs of all containers
|
||||
logs := []map[string]io.ReadCloser{}
|
||||
|
||||
for pod, containers := range podContainersMap {
|
||||
for _, container := range containers {
|
||||
containerLogs, err := o.kubernetesClient.GetPodLogs(pod, container.Name, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logs = append(logs, map[string]io.ReadCloser{container.Name: containerLogs})
|
||||
}
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
// matchOwnerReferenceWithResources recursively checks if the owner reference passed to it matches any of the resources
|
||||
// This is useful when trying to find if a pod is owned by any of the ReplicaSet or Deployment in the cluster.
|
||||
func (o *LogsClient) matchOwnerReferenceWithResources(owner metav1.OwnerReference, resources []unstructured.Unstructured) (bool, error) {
|
||||
// first, check if ownerReference belongs to any of the resources
|
||||
for _, resource := range resources {
|
||||
if resource.GetUID() != "" && owner.UID != "" && resource.GetUID() == owner.UID {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
// second, get the resource indicated by ownerReference and check its ownerReferences field
|
||||
restMapping, err := o.kubernetesClient.GetRestMappingFromGVK(schema.FromAPIVersionAndKind(owner.APIVersion, owner.Kind))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
resource, err := o.kubernetesClient.GetDynamicResource(restMapping.Resource, owner.Name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ownerReferences := resource.GetOwnerReferences()
|
||||
// recursively check if ownerReference matches any of the resources' UID
|
||||
for _, ownerReference := range ownerReferences {
|
||||
return o.matchOwnerReferenceWithResources(ownerReference, resources)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
205
pkg/logs/logs_test.go
Normal file
205
pkg/logs/logs_test.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/kclient"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func fakePod(name string) unstructured.Unstructured {
|
||||
return unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": fmt.Sprintf("pod-%s", name),
|
||||
"uid": fmt.Sprintf("pod-%s", name),
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"containers": map[string]interface{}{
|
||||
"name": fmt.Sprintf("%s-1", name),
|
||||
"image": "image",
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
func fakeDeployment(name string) unstructured.Unstructured {
|
||||
return unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": fmt.Sprintf("deployment-%s", name),
|
||||
"uid": fmt.Sprintf("deployment-%s", name),
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"selector": map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"app": "test",
|
||||
},
|
||||
},
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"app": "test",
|
||||
},
|
||||
},
|
||||
"spec": fakePod(name),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func generateOwnerRefernce(object unstructured.Unstructured) metav1.OwnerReference {
|
||||
return metav1.OwnerReference{
|
||||
APIVersion: object.GetAPIVersion(),
|
||||
Kind: object.GetKind(),
|
||||
Name: object.GetName(),
|
||||
UID: object.GetUID(),
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogsClient_matchOwnerReferenceWithResources_PodsWithOwnerInResources(t *testing.T) {
|
||||
type args struct {
|
||||
resources func() []unstructured.Unstructured
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Case 1: pod owned by a deployment",
|
||||
args: args{
|
||||
resources: func() []unstructured.Unstructured {
|
||||
pod := fakePod("pod")
|
||||
deployment := fakeDeployment("deployment")
|
||||
deployOwnerRef := generateOwnerRefernce(deployment)
|
||||
pod.SetOwnerReferences([]metav1.OwnerReference{deployOwnerRef})
|
||||
return []unstructured.Unstructured{pod, deployment}
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
kubernetesClient := kclient.NewMockClientInterface(ctrl)
|
||||
o := &LogsClient{
|
||||
kubernetesClient: kubernetesClient,
|
||||
}
|
||||
|
||||
got, err := o.matchOwnerReferenceWithResources(tt.args.resources()[0].GetOwnerReferences()[0], tt.args.resources())
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("matchOwnerReferenceWithResources() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("matchOwnerReferenceWithResources() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogsClient_matchOwnerReferenceWithResources_PodsWithNoOwnerInResources(t *testing.T) {
|
||||
// pod and deployment that are not a part of args.resources
|
||||
independentDeploy := fakeDeployment("independent-deploy")
|
||||
independentPod := fakePod("independent-pod")
|
||||
independentPod.SetOwnerReferences([]metav1.OwnerReference{generateOwnerRefernce(independentDeploy)})
|
||||
|
||||
type args struct {
|
||||
resources func() []unstructured.Unstructured
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
gvk *meta.RESTMapping
|
||||
resource *unstructured.Unstructured
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Case 1: Pod not owned by anything in `resources` slice",
|
||||
args: args{
|
||||
resources: func() []unstructured.Unstructured {
|
||||
pod := fakePod("pod")
|
||||
deployment := fakeDeployment("deployment")
|
||||
deployOwnerRef := generateOwnerRefernce(deployment)
|
||||
pod.SetOwnerReferences([]metav1.OwnerReference{deployOwnerRef})
|
||||
return []unstructured.Unstructured{pod, deployment}
|
||||
},
|
||||
},
|
||||
gvk: &meta.RESTMapping{
|
||||
Resource: schema.GroupVersionResource{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Resource: "deployments",
|
||||
},
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
resource: &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"namespace": independentDeploy.GetNamespace(),
|
||||
"name": independentDeploy.GetName(),
|
||||
"uid": independentDeploy.GetUID(),
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"selector": map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"app": "test",
|
||||
},
|
||||
},
|
||||
"template": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
"app": "test",
|
||||
},
|
||||
},
|
||||
"spec": fakePod(independentDeploy.GetName()),
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
kubernetesClient := kclient.NewMockClientInterface(ctrl)
|
||||
kubernetesClient.EXPECT().GetRestMappingFromGVK(
|
||||
schema.FromAPIVersionAndKind(independentDeploy.GetAPIVersion(), independentDeploy.GetKind())).Return(tt.gvk, nil).AnyTimes()
|
||||
kubernetesClient.EXPECT().GetDynamicResource(tt.gvk.Resource, independentDeploy.GetName()).Return(tt.resource, nil).AnyTimes()
|
||||
|
||||
o := &LogsClient{
|
||||
kubernetesClient: kubernetesClient,
|
||||
}
|
||||
got, err := o.matchOwnerReferenceWithResources(independentPod.GetOwnerReferences()[0], tt.args.resources())
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("matchOwnerReferenceWithResources() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("matchOwnerReferenceWithResources() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/logs"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/add"
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/alizer"
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/build_images"
|
||||
@@ -182,6 +184,7 @@ func odoRootCmd(name, fullName string) *cobra.Command {
|
||||
registry.NewCmdRegistry(registry.RecommendedCommandName, util.GetFullName(fullName, registry.RecommendedCommandName)),
|
||||
create.NewCmdCreate(create.RecommendedCommandName, util.GetFullName(fullName, create.RecommendedCommandName)),
|
||||
set.NewCmdSet(set.RecommendedCommandName, util.GetFullName(fullName, set.RecommendedCommandName)),
|
||||
logs.NewCmdLogs(logs.RecommendedCommandName, util.GetFullName(fullName, logs.RecommendedCommandName)),
|
||||
)
|
||||
|
||||
// Add all subcommands to base commands
|
||||
|
||||
158
pkg/odo/cli/logs/logs.go
Normal file
158
pkg/odo/cli/logs/logs.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/devfile/location"
|
||||
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/odo/cmdline"
|
||||
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
|
||||
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
|
||||
"github.com/spf13/cobra"
|
||||
ktemplates "k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
const RecommendedCommandName = "logs"
|
||||
|
||||
type LogsOptions struct {
|
||||
// context
|
||||
Context *genericclioptions.Context
|
||||
// clients
|
||||
clientset *clientset.Clientset
|
||||
|
||||
// variables
|
||||
componentName string
|
||||
contextDir string
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func NewLogsOptions() *LogsOptions {
|
||||
return &LogsOptions{
|
||||
out: log.GetStdout(),
|
||||
}
|
||||
}
|
||||
|
||||
var logsExample = ktemplates.Examples(`
|
||||
# Show logs of all containers
|
||||
%[1]s
|
||||
`)
|
||||
|
||||
func (o *LogsOptions) SetClientset(clientset *clientset.Clientset) {
|
||||
o.clientset = clientset
|
||||
}
|
||||
|
||||
func (o *LogsOptions) Complete(cmdline cmdline.Cmdline, args []string) error {
|
||||
var err error
|
||||
o.contextDir, err = os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isEmptyDir, err := location.DirIsEmpty(o.clientset.FS, o.contextDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isEmptyDir {
|
||||
return errors.New("this command cannot run in an empty directory, run the command in a directory containing source code or initialize using 'odo init'")
|
||||
}
|
||||
|
||||
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(""))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create context: %v", err)
|
||||
}
|
||||
|
||||
o.componentName = o.Context.EnvSpecificInfo.GetDevfileObj().GetMetadataName()
|
||||
|
||||
o.clientset.KubernetesClient.SetNamespace(o.Context.GetProject())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LogsOptions) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LogsOptions) Run(ctx context.Context) error {
|
||||
containersLogs, err := o.clientset.LogsClient.DevModeLogs(o.componentName, o.Context.GetProject())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uniqueContainerNames := map[string]struct{}{}
|
||||
for _, entry := range containersLogs {
|
||||
for container, logs := range entry {
|
||||
uniqueName := getUniqueContainerName(container, uniqueContainerNames)
|
||||
uniqueContainerNames[uniqueName] = struct{}{}
|
||||
err = printLogs(uniqueName, logs, o.out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUniqueContainerName(name string, uniqueNames map[string]struct{}) string {
|
||||
if _, ok := uniqueNames[name]; ok {
|
||||
// name already present in uniqueNames; find another name
|
||||
// first check if last character in name is a number; if so increment it, else append name with 1
|
||||
last, err := strconv.Atoi(string(name[len(name)-1]))
|
||||
if err == nil {
|
||||
last++
|
||||
name = fmt.Sprintf("%s[%d]", name[:len(name)-1], last)
|
||||
} else {
|
||||
last = 1
|
||||
name = fmt.Sprintf("%s[%d]", name, last)
|
||||
}
|
||||
return getUniqueContainerName(name, uniqueNames)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// printLogs prints the logs of the containers with container name prefixed to the log message
|
||||
func printLogs(containerName string, rd io.ReadCloser, out io.Writer) error {
|
||||
color.Set(log.ColorPicker())
|
||||
defer color.Unset()
|
||||
scanner := bufio.NewScanner(rd)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
_, err := fmt.Fprintln(out, containerName+": "+line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewCmdLogs(name, fullname string) *cobra.Command {
|
||||
o := NewLogsOptions()
|
||||
logsCmd := &cobra.Command{
|
||||
Use: name,
|
||||
Short: "Show logs of all containers of the component",
|
||||
Long: `odo logs shows logs of all containers of the component running in the Dev mode.
|
||||
It prefixes each log message with the container name.`,
|
||||
Example: fmt.Sprintf(logsExample, fullname),
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
genericclioptions.GenericRun(o, cmd, args)
|
||||
},
|
||||
}
|
||||
|
||||
clientset.Add(logsCmd, clientset.LOGS, clientset.FILESYSTEM)
|
||||
logsCmd.Annotations["command"] = "main"
|
||||
logsCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
|
||||
return logsCmd
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
package clientset
|
||||
|
||||
import (
|
||||
"github.com/redhat-developer/odo/pkg/logs"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/alizer"
|
||||
@@ -33,6 +34,8 @@ import (
|
||||
const (
|
||||
// ALIZER instantiates client for pkg/alizer
|
||||
ALIZER = "DEP_ALIZER"
|
||||
// BINDING instantiates client for pkg/binding
|
||||
BINDING = "DEP_BINDING"
|
||||
// DELETE_COMPONENT instantiates client for pkg/component/delete
|
||||
DELETE_COMPONENT = "DEP_DELETE_COMPONENT"
|
||||
// DEPLOY instantiates client for pkg/deploy
|
||||
@@ -47,6 +50,8 @@ const (
|
||||
KUBERNETES_NULLABLE = "DEP_KUBERNETES_NULLABLE"
|
||||
// KUBERNETES instantiates client for pkg/kclient
|
||||
KUBERNETES = "DEP_KUBERNETES"
|
||||
// LOGS instantiates client for pkg/logs
|
||||
LOGS = "DEP_LOGS"
|
||||
// PREFERENCE instantiates client for pkg/preference
|
||||
PREFERENCE = "DEP_PREFERENCE"
|
||||
// PROJECT instantiates client for pkg/project
|
||||
@@ -57,8 +62,6 @@ const (
|
||||
STATE = "DEP_STATE"
|
||||
// WATCH instantiates client for pkg/watch
|
||||
WATCH = "DEP_WATCH"
|
||||
// BINDING instantiates client for pkg/binding
|
||||
BINDING = "DEP_BINDING"
|
||||
/* Add key for new package here */
|
||||
)
|
||||
|
||||
@@ -70,6 +73,7 @@ var subdeps map[string][]string = map[string][]string{
|
||||
DEPLOY: {KUBERNETES},
|
||||
DEV: {WATCH},
|
||||
INIT: {ALIZER, FILESYSTEM, PREFERENCE, REGISTRY},
|
||||
LOGS: {KUBERNETES},
|
||||
PROJECT: {KUBERNETES_NULLABLE},
|
||||
REGISTRY: {FILESYSTEM, PREFERENCE},
|
||||
STATE: {FILESYSTEM},
|
||||
@@ -86,6 +90,7 @@ type Clientset struct {
|
||||
FS filesystem.Filesystem
|
||||
InitClient _init.Client
|
||||
KubernetesClient kclient.ClientInterface
|
||||
LogsClient logs.Client
|
||||
PreferenceClient preference.Client
|
||||
ProjectClient project.Client
|
||||
RegistryClient registry.Client
|
||||
@@ -151,6 +156,9 @@ func Fetch(command *cobra.Command) (*Clientset, error) {
|
||||
if isDefined(command, INIT) {
|
||||
dep.InitClient = _init.NewInitClient(dep.FS, dep.PreferenceClient, dep.RegistryClient, dep.AlizerClient)
|
||||
}
|
||||
if isDefined(command, LOGS) {
|
||||
dep.LogsClient = logs.NewLogsClient(dep.KubernetesClient)
|
||||
}
|
||||
if isDefined(command, PROJECT) {
|
||||
dep.ProjectClient = project.NewClient(dep.KubernetesClient)
|
||||
}
|
||||
|
||||
53
tests/integration/devfile/cmd_logs_test.go
Normal file
53
tests/integration/devfile/cmd_logs_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package devfile
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
"github.com/redhat-developer/odo/tests/helper"
|
||||
)
|
||||
|
||||
var _ = Describe("odo logs command tests", func() {
|
||||
var componentName string
|
||||
var commonVar helper.CommonVar
|
||||
|
||||
var _ = BeforeEach(func() {
|
||||
commonVar = helper.CommonBeforeEach()
|
||||
componentName = helper.RandString(6)
|
||||
helper.Chdir(commonVar.Context)
|
||||
Expect(helper.VerifyFileExists(".odo/env/env.yaml")).To(BeFalse())
|
||||
})
|
||||
|
||||
var _ = AfterEach(func() {
|
||||
helper.CommonAfterEach(commonVar)
|
||||
})
|
||||
|
||||
When("directory is empty", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
Expect(helper.ListFilesInDir(commonVar.Context)).To(HaveLen(0))
|
||||
})
|
||||
|
||||
It("should error", func() {
|
||||
output := helper.Cmd("odo", "logs").ShouldFail().Err()
|
||||
Expect(output).To(ContainSubstring("this command cannot run in an empty directory"))
|
||||
})
|
||||
})
|
||||
|
||||
When("component is created and odo logs is executed", func() {
|
||||
BeforeEach(func() {
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
|
||||
helper.Cmd("odo", "init", "--name", componentName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile.yaml")).ShouldPass()
|
||||
Expect(helper.VerifyFileExists(".odo/env/env.yaml")).To(BeFalse())
|
||||
})
|
||||
It("should successfully show logs of the running component", func() {
|
||||
err := helper.RunDevMode(func(session *gexec.Session, outContents []byte, errContents []byte, ports map[string]string) {
|
||||
out := helper.Cmd("odo", "logs").ShouldPass().Out()
|
||||
Expect(out).To(ContainSubstring("runtime: App started on PORT 3000"))
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user