Ignore devstate when existing process name is not odo + delete devstate files with odo delete component (#7090)

* Ignore devstate when existing process name is not odo

* Delete orphan devstate files with odo delete component

* Update unit tests

* Create fake system

* Add unit tests for odo delete component

* Integration tests for odo dev

* Troubleshooting

* First process on Windows is 4

* Use go-ps lib for pidExists
This commit is contained in:
Philippe Martin
2023-09-20 14:20:53 +02:00
committed by GitHub
parent 1ab0178cef
commit 0f828ec99f
32 changed files with 1335 additions and 76 deletions

View File

@@ -15,6 +15,7 @@ import (
"github.com/redhat-developer/odo/pkg/config"
envcontext "github.com/redhat-developer/odo/pkg/config/context"
"github.com/redhat-developer/odo/pkg/odo/cli"
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
@@ -67,6 +68,7 @@ func runCommand(
t.Fatal(err)
}
ctx = envcontext.WithEnvConfig(ctx, *envConfig)
ctx = odocontext.WithPID(ctx, 101)
for k, v := range options.env {
t.Setenv(k, v)

View File

@@ -1,7 +1,9 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
@@ -14,8 +16,10 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/odo/commonflags"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
"github.com/redhat-developer/odo/pkg/podman"
"github.com/redhat-developer/odo/pkg/state"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
"github.com/redhat-developer/odo/pkg/util"
"github.com/redhat-developer/odo/tests/helper"
@@ -95,8 +99,9 @@ var nodeJsSourcesFsContent fscontentFunc = func(fs filesystem.Filesystem) error
}
type fsOptions struct {
dotOdoExists bool
generated []string
dotOdoExists bool
generated []string
devstateFiles []state.Content
}
var nodeJsSourcesAndDevfileFsContent = func(devfilePath string, options fsOptions) fscontentFunc {
@@ -114,6 +119,21 @@ var nodeJsSourcesAndDevfileFsContent = func(devfilePath string, options fsOption
gomega.Expect(err).NotTo(gomega.HaveOccurred())
}
for _, devstateFile := range options.devstateFiles {
jsonContent, err := json.MarshalIndent(devstateFile, "", " ")
if err != nil {
return err
}
dir := filepath.Dir(util.DotOdoDirectory)
err = os.MkdirAll(dir, 0750)
if err != nil {
return err
}
err = fs.WriteFile(fmt.Sprintf("./.odo/devstate.%d.json", devstateFile.PID), jsonContent, 0644)
if err != nil {
return err
}
}
return nil
}
}
@@ -250,9 +270,9 @@ func TestOdoDeleteMatrix(t *testing.T) {
nameOptions map[string]nameOption
wantErr string
checkOutput func(t *testing.T, s string)
checkOutput func(t *testing.T, testContext testContext, s string)
checkFS func(t *testing.T, fs filesystem.Filesystem)
checkCalls func(t *testing.T, clientset clientset.Clientset, tetsContext testContext)
checkCalls func(t *testing.T, clientset clientset.Clientset, testContext testContext)
}{
{
name: "delete component when Devfile is not present in the directory",
@@ -324,7 +344,7 @@ func TestOdoDeleteMatrix(t *testing.T) {
"no": noNameOptions,
},
checkOutput: func(t *testing.T, s string) {
checkOutput: func(t *testing.T, testContext testContext, s string) {
gomega.Expect(s).ToNot(gomega.ContainSubstring("devfile.yaml"), "should not list the devfile.yaml")
},
checkFS: func(t *testing.T, fs filesystem.Filesystem) {
@@ -353,7 +373,7 @@ func TestOdoDeleteMatrix(t *testing.T) {
"no": noNameOptions,
},
checkOutput: func(t *testing.T, s string) {
checkOutput: func(t *testing.T, testContext testContext, s string) {
gomega.Expect(s).To(gomega.ContainSubstring("devfile.yaml"), "should list the devfile.yaml")
},
checkFS: func(t *testing.T, fs filesystem.Filesystem) {
@@ -379,6 +399,17 @@ func TestOdoDeleteMatrix(t *testing.T) {
fsOptions{
generated: []string{"devfile.yaml"},
}),
"nodeJS sources, Devfile and orphan devstate file": nodeJsSourcesAndDevfileFsContent(
filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
fsOptions{
dotOdoExists: true,
devstateFiles: []state.Content{
{
PID: 43,
Platform: commonflags.PlatformPodman,
},
},
}),
},
runningInOptions: allRunningInOptions,
filesOptions: allFilesOptions,
@@ -386,8 +417,14 @@ func TestOdoDeleteMatrix(t *testing.T) {
"no": noNameOptions,
},
checkOutput: func(t *testing.T, s string) {
checkOutput: func(t *testing.T, testContext testContext, s string) {
gomega.Expect(s).To(gomega.ContainSubstring("No resource found for component %q", "my-component"))
if testContext.fscontent == "nodeJS sources, Devfile and orphan devstate file" &&
testContext.runningInOption != "deploy" {
gomega.Expect(s).To(gomega.ContainSubstring("devstate.43.json"))
} else {
gomega.Expect(s).To(gomega.Not(gomega.ContainSubstring("devstate.43.json")))
}
},
checkCalls: checkCallsNonDeployedComponent,
},
@@ -411,6 +448,17 @@ func TestOdoDeleteMatrix(t *testing.T) {
fsOptions{
generated: []string{"devfile.yaml"},
}),
"nodeJS sources, Devfile and orphan devstate file": nodeJsSourcesAndDevfileFsContent(
filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
fsOptions{
dotOdoExists: true,
devstateFiles: []state.Content{
{
PID: 43,
Platform: commonflags.PlatformPodman,
},
},
}),
},
runningInOptions: map[string]runningInOption{
"no": noRunningInOption,
@@ -421,9 +469,15 @@ func TestOdoDeleteMatrix(t *testing.T) {
"no": noNameOptions,
},
checkOutput: func(t *testing.T, s string) {
checkOutput: func(t *testing.T, testContext testContext, s string) {
gomega.Expect(s).To(gomega.ContainSubstring("The following pods and associated volumes will get deleted from podman"))
gomega.Expect(s).To(gomega.ContainSubstring("- my-component-app"))
if testContext.fscontent == "nodeJS sources, Devfile and orphan devstate file" &&
testContext.runningInOption != "deploy" {
gomega.Expect(s).To(gomega.ContainSubstring("devstate.43.json"))
} else {
gomega.Expect(s).To(gomega.Not(gomega.ContainSubstring("devstate.43.json")))
}
},
checkCalls: checkCallsDeployedComponent,
},
@@ -447,6 +501,17 @@ func TestOdoDeleteMatrix(t *testing.T) {
fsOptions{
generated: []string{"devfile.yaml"},
}),
"nodeJS sources, Devfile and orphan devstate file": nodeJsSourcesAndDevfileFsContent(
filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
fsOptions{
dotOdoExists: true,
devstateFiles: []state.Content{
{
PID: 43,
Platform: commonflags.PlatformPodman,
},
},
}),
},
runningInOptions: map[string]runningInOption{
"no": noRunningInOption,
@@ -457,9 +522,15 @@ func TestOdoDeleteMatrix(t *testing.T) {
"no": noNameOptions,
},
checkOutput: func(t *testing.T, s string) {
checkOutput: func(t *testing.T, testContext testContext, s string) {
gomega.Expect(s).To(gomega.ContainSubstring("The following resources will get deleted from cluster"))
gomega.Expect(s).To(gomega.ContainSubstring("- Deployment: my-component-app"))
if testContext.fscontent == "nodeJS sources, Devfile and orphan devstate file" &&
testContext.runningInOption != "deploy" {
gomega.Expect(s).To(gomega.ContainSubstring("devstate.43.json"))
} else {
gomega.Expect(s).To(gomega.Not(gomega.ContainSubstring("devstate.43.json")))
}
},
checkCalls: checkCallsDeployedComponent,
},
@@ -514,6 +585,7 @@ func TestOdoDeleteMatrix(t *testing.T) {
t.Fatalf(message)
})
clientset := clientset.Clientset{}
clientset.FS = filesystem.DefaultFs{}
env := map[string]string{}
config := map[string]string{}
platformFunc(t, env, config, &clientset)
@@ -535,7 +607,7 @@ func TestOdoDeleteMatrix(t *testing.T) {
}
}
if tt.checkOutput != nil {
tt.checkOutput(t, stdout)
tt.checkOutput(t, testCtx, stdout)
}
if tt.checkFS != nil {

View File

@@ -188,6 +188,30 @@ Various factors are responsible for this:
Please refer to [Troubleshoot Storage Permission issues on managed cloud providers clusters](./user-guides/advanced/using-odo-with-other-clusters.md) for possible solutions to fix this.
### Orphan Devstate files
An `odo dev` session creates a `.odo/devstate.<PID>.json` file when the session starts, and deletes it at the end of the session.
If the session terminates abrupty, the state file won't be deleted, and will remain in the `.odo` directory.
You can delete such orphan devstate files using the command `odo delete component`.
<details>
<summary>Example output</summary>
```shell
$ odo delete component
Searching resources to delete, please wait...
This will delete "go" from podman.
• The following pods and associated volumes will get deleted from podman:
• - go-app
This will delete the following files and directories:
- /home/user/projects/go/.odo/devstate.83932.json
- /home/user/projects/go/.odo/devstate.json
```
</details>
## Podman Issues
### `odo` says it cannot access Podman

1
go.mod
View File

@@ -151,6 +151,7 @@ require (
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/moby/buildkit v0.11.6 // indirect

2
go.sum generated
View File

@@ -882,6 +882,8 @@ github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=

View File

@@ -310,9 +310,14 @@ func (o *ComponentOptions) deleteDevfileComponent(ctx context.Context) ([]unstru
hasPodmanResources = len(podmanPods) != 0
}
orphans, err := o.getOrphanDevstateFiles(o.clientset.FS, ctx)
if err != nil {
return nil, err
}
if !(hasClusterResources || hasPodmanResources) {
log.Finfof(o.clientset.Stdout, messageWithPlatforms(o.clientset.KubernetesClient != nil, o.clientset.PodmanClient != nil, componentName, namespace))
if !o.withFilesFlag {
if !o.withFilesFlag && len(orphans) == 0 {
// check for resources here
return remainingResources, nil
}
@@ -326,9 +331,15 @@ func (o *ComponentOptions) deleteDevfileComponent(ctx context.Context) ([]unstru
if err != nil {
return nil, err
}
}
filesToDelete = append(filesToDelete, orphans...)
hasFilesToDelete := len(filesToDelete) != 0
if hasFilesToDelete {
o.printFileCreatedByOdo(filesToDelete, hasClusterResources)
}
hasFilesToDelete := len(filesToDelete) != 0
if !(hasClusterResources || hasPodmanResources || hasFilesToDelete) {
klog.V(2).Info("no cluster resources and no files to delete")
@@ -386,7 +397,7 @@ func (o *ComponentOptions) deleteDevfileComponent(ctx context.Context) ([]unstru
log.Finfof(o.clientset.Stdout, "The component %q is successfully deleted from podman", componentName)
}
if o.withFilesFlag {
if o.withFilesFlag || len(orphans) > 0 {
// Delete files
remainingFiles := o.deleteFilesCreatedByOdo(o.clientset.FS, filesToDelete)
var listOfFiles []string
@@ -495,6 +506,20 @@ func getFilesCreatedByOdo(filesys filesystem.Filesystem, ctx context.Context) ([
return list, nil
}
// getOrphanDevstateFiles gets the list of all Devstate files for which no odo process exists
func (o *ComponentOptions) getOrphanDevstateFiles(filesys filesystem.Filesystem, ctx context.Context) ([]string, error) {
var list []string
if o.runningIn != labels.ComponentDeployMode {
var orphanDevstates []string
orphanDevstates, err := o.clientset.StateClient.GetOrphanFiles(ctx)
if err != nil {
return nil, err
}
list = append(list, orphanDevstates...)
}
return list, nil
}
func (o *ComponentOptions) printFileCreatedByOdo(files []string, hasClusterResources bool) {
if len(files) == 0 {
return
@@ -544,7 +569,7 @@ func NewCmdComponent(ctx context.Context, name, fullName string, testClientset c
componentCmd.Flags().BoolVarP(&o.withFilesFlag, "files", "", false, "Delete all files and directories generated by odo. Use with caution.")
componentCmd.Flags().BoolVarP(&o.forceFlag, "force", "f", false, "Delete component without prompting")
componentCmd.Flags().BoolVarP(&o.waitFlag, "wait", "w", false, "Wait for deletion of all dependent resources")
clientset.Add(componentCmd, clientset.DELETE_COMPONENT, clientset.KUBERNETES, clientset.FILESYSTEM)
clientset.Add(componentCmd, clientset.DELETE_COMPONENT, clientset.KUBERNETES, clientset.FILESYSTEM, clientset.STATE)
if feature.IsEnabled(ctx, feature.GenericPlatformFlag) {
clientset.Add(componentCmd, clientset.PODMAN_NULLABLE)
}

View File

@@ -9,7 +9,6 @@ import (
"testing"
"github.com/devfile/library/v2/pkg/devfile/parser"
"github.com/devfile/library/v2/pkg/testingutil/filesystem"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
corev1 "k8s.io/api/core/v1"
@@ -23,7 +22,10 @@ import (
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
"github.com/redhat-developer/odo/pkg/podman"
"github.com/redhat-developer/odo/pkg/state"
"github.com/redhat-developer/odo/pkg/testingutil"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
"github.com/redhat-developer/odo/pkg/testingutil/system"
)
func TestComponentOptions_deleteNamedComponent(t *testing.T) {
@@ -420,6 +422,7 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
remainingResources []unstructured.Unstructured
wantErr bool
deleteClient func(ctrl *gomock.Controller) _delete.Client
stateClient func(ctrl *gomock.Controller) state.Client
}{
{
name: "deleting a component with access to devfile",
@@ -433,6 +436,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().DeleteResources(resources, false).Return([]unstructured.Unstructured{})
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
},
@@ -450,6 +458,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().DeleteResources(resources, false).Return([]unstructured.Unstructured{})
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
runningIn: labels.ComponentDevMode,
@@ -468,6 +481,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().DeleteResources(resources, false).Return([]unstructured.Unstructured{})
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
runningIn: labels.ComponentDeployMode,
@@ -484,6 +502,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
Return(resources, nil)
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
runningIn: labels.ComponentDeployMode,
@@ -501,6 +524,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().DeleteResources(resources, false).Return(nil)
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
},
@@ -513,6 +541,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().ListClusterResourcesToDeleteFromDevfile(gomock.Any(), appName, gomock.Any(), labels.ComponentAnyMode).Return(false, nil, errors.New("some error"))
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
},
@@ -525,6 +558,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().ListClusterResourcesToDeleteFromDevfile(gomock.Any(), appName, gomock.Any(), labels.ComponentDevMode).Return(false, nil, errors.New("some error"))
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
runningIn: labels.ComponentDevMode,
@@ -538,6 +576,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().ListClusterResourcesToDeleteFromDevfile(gomock.Any(), appName, gomock.Any(), labels.ComponentDeployMode).Return(false, nil, errors.New("some error"))
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
runningIn: labels.ComponentDeployMode,
@@ -552,6 +595,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().ListClusterResourcesToDelete(gomock.Any(), gomock.Any(), gomock.Any(), labels.ComponentAnyMode)
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: false,
},
@@ -565,6 +613,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().ListClusterResourcesToDelete(gomock.Any(), gomock.Any(), gomock.Any(), labels.ComponentDevMode)
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: false,
runningIn: labels.ComponentDevMode,
@@ -579,6 +632,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().ListClusterResourcesToDelete(gomock.Any(), gomock.Any(), gomock.Any(), labels.ComponentDeployMode)
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: false,
runningIn: labels.ComponentDeployMode,
@@ -594,6 +652,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().ListClusterResourcesToDelete(gomock.Any(), gomock.Any(), gomock.Any(), labels.ComponentAnyMode)
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
},
@@ -608,6 +671,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().ListClusterResourcesToDelete(gomock.Any(), gomock.Any(), gomock.Any(), labels.ComponentDevMode)
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
runningIn: labels.ComponentDevMode,
@@ -623,6 +691,11 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
deleteClient.EXPECT().ListClusterResourcesToDelete(gomock.Any(), gomock.Any(), gomock.Any(), labels.ComponentDeployMode)
return deleteClient
},
stateClient: func(ctrl *gomock.Controller) state.Client {
fs := filesystem.NewFakeFs()
system := system.Default{}
return state.NewStateClient(fs, system)
},
fields: fields{
forceFlag: true,
runningIn: labels.ComponentDeployMode,
@@ -640,6 +713,7 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
ctrl := gomock.NewController(t)
kubeClient := prepareKubeClient(ctrl, projectName)
deleteClient := tt.deleteClient(ctrl)
stateClient := tt.stateClient(ctrl)
o := &ComponentOptions{
name: tt.fields.name,
forceFlag: tt.fields.forceFlag,
@@ -649,6 +723,7 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
Stderr: os.Stderr,
KubernetesClient: kubeClient,
DeleteClient: deleteClient,
StateClient: stateClient,
},
}
ctx := odocontext.WithNamespace(context.Background(), projectName)
@@ -656,6 +731,7 @@ func TestComponentOptions_deleteDevfileComponent(t *testing.T) {
ctx = odocontext.WithWorkingDirectory(ctx, workingDir)
ctx = odocontext.WithComponentName(ctx, compName)
ctx = odocontext.WithEffectiveDevfileObj(ctx, &info)
ctx = odocontext.WithPID(ctx, 101)
remainingResources, err := o.deleteDevfileComponent(ctx)
if (err != nil) != tt.wantErr {
t.Errorf("deleteDevfileComponent() error = %v, wantErr %v", err, tt.wantErr)

View File

@@ -44,6 +44,7 @@ import (
"github.com/redhat-developer/odo/pkg/project"
"github.com/redhat-developer/odo/pkg/registry"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
"github.com/redhat-developer/odo/pkg/testingutil/system"
"github.com/redhat-developer/odo/pkg/watch"
)
@@ -90,6 +91,8 @@ const (
STATE = "DEP_STATE"
// SYNC instantiates client for pkg/sync
SYNC = "DEP_SYNC"
// SYSTEM instantiates client for pkg/testingutil/system
SYSTEM = "DEP_SYSTEM"
// WATCH instantiates client for pkg/watch
WATCH = "DEP_WATCH"
/* Add key for new package here */
@@ -122,7 +125,7 @@ var subdeps map[string][]string = map[string][]string{
PORT_FORWARD: {KUBERNETES_NULLABLE, EXEC, STATE},
PROJECT: {KUBERNETES},
REGISTRY: {FILESYSTEM, PREFERENCE, KUBERNETES_NULLABLE},
STATE: {FILESYSTEM},
STATE: {FILESYSTEM, SYSTEM},
SYNC: {EXEC},
WATCH: {INFORMER, KUBERNETES_NULLABLE},
BINDING: {PROJECT, KUBERNETES_NULLABLE},
@@ -152,6 +155,7 @@ type Clientset struct {
RegistryClient registry.Client
StateClient state.Client
SyncClient sync.Client
systemClient system.System
WatchClient watch.Client
/* Add client by alphabetic order */
}
@@ -201,6 +205,13 @@ func Fetch(command *cobra.Command, platform string, testClientset Clientset) (*C
dep.FS = filesystem.DefaultFs{}
}
}
if isDefined(command, SYSTEM) {
if testClientset.systemClient != nil {
dep.systemClient = testClientset.systemClient
} else {
dep.systemClient = system.Default{}
}
}
if isDefined(command, INFORMER) {
dep.InformerClient = informer.NewInformerClient()
}
@@ -289,7 +300,7 @@ func Fetch(command *cobra.Command, platform string, testClientset Clientset) (*C
dep.ProjectClient = project.NewClient(dep.KubernetesClient)
}
if isDefined(command, STATE) {
dep.StateClient = state.NewStateClient(dep.FS)
dep.StateClient = state.NewStateClient(dep.FS, dep.systemClient)
}
if isDefined(command, SYNC) {
switch platform {

View File

@@ -24,4 +24,6 @@ type Client interface {
// GetAPIServerPorts returns the port where the API servers are listening, possibly per platform.
GetAPIServerPorts(ctx context.Context) ([]api.DevControlPlane, error)
GetOrphanFiles(ctx context.Context) ([]string, error)
}

View File

@@ -1,38 +0,0 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
package state
import (
"fmt"
"os"
"syscall"
)
func pidExists(pid int) (bool, error) {
if pid <= 0 {
return false, fmt.Errorf("invalid pid %v", pid)
}
proc, err := os.FindProcess(pid)
if err != nil {
return false, nil
}
err = proc.Signal(syscall.Signal(0))
if err == nil {
return true, nil
}
if err.Error() == "os: process already finished" {
return false, nil
}
errno, ok := err.(syscall.Errno)
if !ok {
return false, err
}
switch errno {
case syscall.ESRCH:
return false, nil
case syscall.EPERM:
return true, nil
}
return false, err
}

View File

@@ -1,17 +0,0 @@
package state
import (
"fmt"
"os"
)
func pidExists(pid int) (bool, error) {
if pid <= 0 {
return false, fmt.Errorf("invalid pid %v", pid)
}
_, err := os.FindProcess(pid)
if err != nil {
return false, nil
}
return true, nil
}

View File

@@ -10,24 +10,30 @@ import (
"path/filepath"
"regexp"
"github.com/mitchellh/go-ps"
"k8s.io/klog"
"github.com/redhat-developer/odo/pkg/api"
"github.com/redhat-developer/odo/pkg/odo/cli/feature"
"github.com/redhat-developer/odo/pkg/odo/commonflags"
fcontext "github.com/redhat-developer/odo/pkg/odo/commonflags/context"
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
"github.com/redhat-developer/odo/pkg/testingutil/system"
)
type State struct {
content Content
fs filesystem.Filesystem
system system.System
}
var _ Client = (*State)(nil)
func NewStateClient(fs filesystem.Filesystem) *State {
func NewStateClient(fs filesystem.Filesystem, system system.System) *State {
return &State{
fs: fs,
fs: fs,
system: system,
}
}
@@ -246,7 +252,7 @@ func (o *State) isFreeOrOwnedBy(pid int) (bool, error) {
return true, nil
}
exists, err := pidExists(savedContent.PID)
exists, err := o.system.PidExists(savedContent.PID)
if err != nil {
return false, err
}
@@ -292,14 +298,99 @@ func (o *State) checkFirstInPlatform(ctx context.Context) error {
if content.PID == pid {
continue
}
exists, err := pidExists(content.PID)
exists, err := o.system.PidExists(content.PID)
if err != nil {
return err
}
if exists {
var process ps.Process
process, err = o.system.FindProcess(content.PID)
if err != nil {
klog.V(4).Infof("process %d exists but is not accessible, ignoring", content.PID)
continue
}
if process.Executable() != "odo" && process.Executable() != "odo.exe" {
klog.V(4).Infof("process %d exists but is not odo, ignoring", content.PID)
continue
}
// Process exists => problem
return NewErrAlreadyRunningOnPlatform(platform, content.PID)
}
}
return nil
}
func (o *State) GetOrphanFiles(ctx context.Context) ([]string, error) {
var (
pid = odocontext.GetPID(ctx)
result []string
)
re := regexp.MustCompile(`^devstate\.?[0-9]*\.json$`)
entries, err := o.fs.ReadDir(_dirpath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
// No file found => no orphan files
return nil, nil
}
return nil, err
}
for _, entry := range entries {
if !re.MatchString(entry.Name()) {
continue
}
filename, err := getFullFilename(entry)
if err != nil {
return nil, err
}
jsonContent, err := o.fs.ReadFile(filepath.Join(_dirpath, entry.Name()))
if err != nil {
return nil, err
}
var content Content
// Ignore error, to handle empty file
_ = json.Unmarshal(jsonContent, &content)
if content.PID == pid {
continue
}
if content.PID == 0 {
// This is devstate.json with pid=0
continue
}
exists, err := o.system.PidExists(content.PID)
if err != nil {
return nil, err
}
if exists {
var process ps.Process
process, err = o.system.FindProcess(content.PID)
if err != nil {
klog.V(4).Infof("process %d exists but is not accessible => orphan", content.PID)
result = append(result, filename)
continue
}
if process == nil {
klog.V(4).Infof("process %d does not exist => orphan", content.PID)
result = append(result, filename)
continue
}
if process.Executable() != "odo" && process.Executable() != "odo.exe" {
klog.V(4).Infof("process %d exists but is not odo => orphan", content.PID)
result = append(result, filename)
continue
}
// Process exists => not orphan
klog.V(4).Infof("process %d exists and is odo => not orphan", content.PID)
continue
}
klog.V(4).Infof("process %d does not exist => orphan", content.PID)
result = append(result, filename)
}
return result, nil
}
func getFullFilename(entry fs.FileInfo) (string, error) {
return filepath.Abs(filepath.Join(_dirpath, entry.Name()))
}

View File

@@ -0,0 +1,24 @@
package system
import "github.com/mitchellh/go-ps"
type Default struct{}
var _ System = Default{}
func (o Default) FindProcess(pid int) (ps.Process, error) {
return ps.FindProcess(pid)
}
func (o Default) PidExists(pid int) (bool, error) {
processes, err := ps.Processes()
if err != nil {
return false, err
}
for _, process := range processes {
if process.Pid() == pid {
return true, nil
}
}
return false, nil
}

View File

@@ -0,0 +1,41 @@
package system
import (
"errors"
"github.com/mitchellh/go-ps"
)
type Fake struct {
ProcessId int
ParentId int
// PidTable is a map of pid => executable name of existing processes
PidTable map[int]string
}
func (o Fake) Pid() int {
return o.ProcessId
}
func (o Fake) PPid() int {
return o.ParentId
}
func (o Fake) Executable() string {
return o.PidTable[o.ProcessId]
}
var _ System = Fake{}
func (o Fake) FindProcess(pid int) (ps.Process, error) {
if _, found := o.PidTable[pid]; found {
o.ProcessId = pid
return o, nil
}
return nil, errors.New("no process found")
}
func (o Fake) PidExists(pid int) (bool, error) {
_, found := o.PidTable[pid]
return found, nil
}

View File

@@ -0,0 +1,8 @@
package system
import "github.com/mitchellh/go-ps"
type System interface {
FindProcess(pid int) (ps.Process, error)
PidExists(pid int) (bool, error)
}

View File

@@ -133,6 +133,7 @@ type DevSessionOpts struct {
APIServerPort int
SyncGitDir bool
ShowLogs bool
VerboseLevel string
}
// StartDevMode starts a dev session with `odo dev`
@@ -174,6 +175,9 @@ func StartDevMode(options DevSessionOpts) (devSession DevSession, err error) {
if options.ShowLogs {
args = append(args, "--logs")
}
if options.VerboseLevel != "" {
args = append(args, "-v", options.VerboseLevel)
}
args = append(args, options.CmdlineArgs...)
cmd := Cmd("odo", args...)
cmd.Cmd.Stdin = c.Tty()
@@ -220,6 +224,10 @@ func (o DevSession) Kill() {
o.session.Kill()
}
func (o DevSession) PID() int {
return o.session.Command.Process.Pid
}
// Stop a Dev session cleanly (equivalent as hitting Ctrl-c)
func (o *DevSession) Stop() {
if o.session == nil {

View File

@@ -15,3 +15,7 @@ func terminateProc(session *gexec.Session) error {
}
func setSysProcAttr(command *exec.Cmd) {}
func GetFirstProcess() int {
return 1
}

View File

@@ -35,3 +35,7 @@ func setSysProcAttr(command *exec.Cmd) {
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
}
}
func GetFirstProcess() int {
return 4
}

View File

@@ -590,6 +590,36 @@ ComponentSettings:
})
})
When("killing odo dev and another process replaces it", func() {
var newDevSession helper.DevSession
BeforeEach(func() {
pid := devSession.PID()
devSession.Kill()
devSession.WaitEnd()
devstate := fmt.Sprintf(".odo/devstate.%d.json", pid)
newdevstate := fmt.Sprintf(".odo/devstate.%d.json", helper.GetFirstProcess())
helper.ReplaceString(
devstate,
fmt.Sprintf("\"pid\": %d", pid),
fmt.Sprintf("\"pid\": %d", helper.GetFirstProcess()))
err := os.Rename(devstate, newdevstate)
Expect(err).ToNot(HaveOccurred())
})
It("should restart a new session successfully", func() {
var err error
newDevSession, err = helper.StartDevMode(helper.DevSessionOpts{
VerboseLevel: "4",
})
Expect(err).ToNot(HaveOccurred())
logMsg := fmt.Sprintf("process %d exists but is not odo, ignoring", helper.GetFirstProcess())
Expect(newDevSession.ErrOut).Should(ContainSubstring(logMsg))
newDevSession.Stop()
newDevSession.WaitEnd()
})
})
When("stopping odo dev normally", func() {
BeforeEach(func() {
devSession.Stop()

1
vendor/github.com/mitchellh/go-ps/.gitignore generated vendored Normal file
View File

@@ -0,0 +1 @@
.vagrant/

4
vendor/github.com/mitchellh/go-ps/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,4 @@
language: go
go:
- 1.2.1

21
vendor/github.com/mitchellh/go-ps/LICENSE.md generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

34
vendor/github.com/mitchellh/go-ps/README.md generated vendored Normal file
View File

@@ -0,0 +1,34 @@
# Process List Library for Go [![GoDoc](https://godoc.org/github.com/mitchellh/go-ps?status.png)](https://godoc.org/github.com/mitchellh/go-ps)
go-ps is a library for Go that implements OS-specific APIs to list and
manipulate processes in a platform-safe way. The library can find and
list processes on Linux, Mac OS X, Solaris, and Windows.
If you're new to Go, this library has a good amount of advanced Go educational
value as well. It uses some advanced features of Go: build tags, accessing
DLL methods for Windows, cgo for Darwin, etc.
How it works:
* **Darwin** uses the `sysctl` syscall to retrieve the process table.
* **Unix** uses the procfs at `/proc` to inspect the process tree.
* **Windows** uses the Windows API, and methods such as
`CreateToolhelp32Snapshot` to get a point-in-time snapshot of
the process table.
## Installation
Install using standard `go get`:
```
$ go get github.com/mitchellh/go-ps
...
```
## TODO
Want to contribute? Here is a short TODO list of things that aren't
implemented for this library that would be nice:
* FreeBSD support
* Plan9 support

43
vendor/github.com/mitchellh/go-ps/Vagrantfile generated vendored Normal file
View File

@@ -0,0 +1,43 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "chef/ubuntu-12.04"
config.vm.provision "shell", inline: $script
["vmware_fusion", "vmware_workstation"].each do |p|
config.vm.provider "p" do |v|
v.vmx["memsize"] = "1024"
v.vmx["numvcpus"] = "2"
v.vmx["cpuid.coresPerSocket"] = "1"
end
end
end
$script = <<SCRIPT
SRCROOT="/opt/go"
# Install Go
sudo apt-get update
sudo apt-get install -y build-essential mercurial
sudo hg clone -u release https://code.google.com/p/go ${SRCROOT}
cd ${SRCROOT}/src
sudo ./all.bash
# Setup the GOPATH
sudo mkdir -p /opt/gopath
cat <<EOF >/tmp/gopath.sh
export GOPATH="/opt/gopath"
export PATH="/opt/go/bin:\$GOPATH/bin:\$PATH"
EOF
sudo mv /tmp/gopath.sh /etc/profile.d/gopath.sh
sudo chmod 0755 /etc/profile.d/gopath.sh
# Make sure the gopath is usable by bamboo
sudo chown -R vagrant:vagrant $SRCROOT
sudo chown -R vagrant:vagrant /opt/gopath
SCRIPT

40
vendor/github.com/mitchellh/go-ps/process.go generated vendored Normal file
View File

@@ -0,0 +1,40 @@
// ps provides an API for finding and listing processes in a platform-agnostic
// way.
//
// NOTE: If you're reading these docs online via GoDocs or some other system,
// you might only see the Unix docs. This project makes heavy use of
// platform-specific implementations. We recommend reading the source if you
// are interested.
package ps
// Process is the generic interface that is implemented on every platform
// and provides common operations for processes.
type Process interface {
// Pid is the process ID for this process.
Pid() int
// PPid is the parent process ID for this process.
PPid() int
// Executable name running this process. This is not a path to the
// executable.
Executable() string
}
// Processes returns all processes.
//
// This of course will be a point-in-time snapshot of when this method was
// called. Some operating systems don't provide snapshot capability of the
// process table, in which case the process table returned might contain
// ephemeral entities that happened to be running when this was called.
func Processes() ([]Process, error) {
return processes()
}
// FindProcess looks up a single process by pid.
//
// Process will be nil and error will be nil if a matching process is
// not found.
func FindProcess(pid int) (Process, error) {
return findProcess(pid)
}

138
vendor/github.com/mitchellh/go-ps/process_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,138 @@
// +build darwin
package ps
import (
"bytes"
"encoding/binary"
"syscall"
"unsafe"
)
type DarwinProcess struct {
pid int
ppid int
binary string
}
func (p *DarwinProcess) Pid() int {
return p.pid
}
func (p *DarwinProcess) PPid() int {
return p.ppid
}
func (p *DarwinProcess) Executable() string {
return p.binary
}
func findProcess(pid int) (Process, error) {
ps, err := processes()
if err != nil {
return nil, err
}
for _, p := range ps {
if p.Pid() == pid {
return p, nil
}
}
return nil, nil
}
func processes() ([]Process, error) {
buf, err := darwinSyscall()
if err != nil {
return nil, err
}
procs := make([]*kinfoProc, 0, 50)
k := 0
for i := _KINFO_STRUCT_SIZE; i < buf.Len(); i += _KINFO_STRUCT_SIZE {
proc := &kinfoProc{}
err = binary.Read(bytes.NewBuffer(buf.Bytes()[k:i]), binary.LittleEndian, proc)
if err != nil {
return nil, err
}
k = i
procs = append(procs, proc)
}
darwinProcs := make([]Process, len(procs))
for i, p := range procs {
darwinProcs[i] = &DarwinProcess{
pid: int(p.Pid),
ppid: int(p.PPid),
binary: darwinCstring(p.Comm),
}
}
return darwinProcs, nil
}
func darwinCstring(s [16]byte) string {
i := 0
for _, b := range s {
if b != 0 {
i++
} else {
break
}
}
return string(s[:i])
}
func darwinSyscall() (*bytes.Buffer, error) {
mib := [4]int32{_CTRL_KERN, _KERN_PROC, _KERN_PROC_ALL, 0}
size := uintptr(0)
_, _, errno := syscall.Syscall6(
syscall.SYS___SYSCTL,
uintptr(unsafe.Pointer(&mib[0])),
4,
0,
uintptr(unsafe.Pointer(&size)),
0,
0)
if errno != 0 {
return nil, errno
}
bs := make([]byte, size)
_, _, errno = syscall.Syscall6(
syscall.SYS___SYSCTL,
uintptr(unsafe.Pointer(&mib[0])),
4,
uintptr(unsafe.Pointer(&bs[0])),
uintptr(unsafe.Pointer(&size)),
0,
0)
if errno != 0 {
return nil, errno
}
return bytes.NewBuffer(bs[0:size]), nil
}
const (
_CTRL_KERN = 1
_KERN_PROC = 14
_KERN_PROC_ALL = 0
_KINFO_STRUCT_SIZE = 648
)
type kinfoProc struct {
_ [40]byte
Pid int32
_ [199]byte
Comm [16]byte
_ [301]byte
PPid int32
_ [84]byte
}

260
vendor/github.com/mitchellh/go-ps/process_freebsd.go generated vendored Normal file
View File

@@ -0,0 +1,260 @@
// +build freebsd
package ps
import (
"bytes"
"encoding/binary"
"syscall"
"unsafe"
)
// copied from sys/sysctl.h
const (
CTL_KERN = 1 // "high kernel": proc, limits
KERN_PROC = 14 // struct: process entries
KERN_PROC_PID = 1 // by process id
KERN_PROC_PROC = 8 // only return procs
KERN_PROC_PATHNAME = 12 // path to executable
)
// copied from sys/user.h
type Kinfo_proc struct {
Ki_structsize int32
Ki_layout int32
Ki_args int64
Ki_paddr int64
Ki_addr int64
Ki_tracep int64
Ki_textvp int64
Ki_fd int64
Ki_vmspace int64
Ki_wchan int64
Ki_pid int32
Ki_ppid int32
Ki_pgid int32
Ki_tpgid int32
Ki_sid int32
Ki_tsid int32
Ki_jobc [2]byte
Ki_spare_short1 [2]byte
Ki_tdev int32
Ki_siglist [16]byte
Ki_sigmask [16]byte
Ki_sigignore [16]byte
Ki_sigcatch [16]byte
Ki_uid int32
Ki_ruid int32
Ki_svuid int32
Ki_rgid int32
Ki_svgid int32
Ki_ngroups [2]byte
Ki_spare_short2 [2]byte
Ki_groups [64]byte
Ki_size int64
Ki_rssize int64
Ki_swrss int64
Ki_tsize int64
Ki_dsize int64
Ki_ssize int64
Ki_xstat [2]byte
Ki_acflag [2]byte
Ki_pctcpu int32
Ki_estcpu int32
Ki_slptime int32
Ki_swtime int32
Ki_cow int32
Ki_runtime int64
Ki_start [16]byte
Ki_childtime [16]byte
Ki_flag int64
Ki_kiflag int64
Ki_traceflag int32
Ki_stat [1]byte
Ki_nice [1]byte
Ki_lock [1]byte
Ki_rqindex [1]byte
Ki_oncpu [1]byte
Ki_lastcpu [1]byte
Ki_ocomm [17]byte
Ki_wmesg [9]byte
Ki_login [18]byte
Ki_lockname [9]byte
Ki_comm [20]byte
Ki_emul [17]byte
Ki_sparestrings [68]byte
Ki_spareints [36]byte
Ki_cr_flags int32
Ki_jid int32
Ki_numthreads int32
Ki_tid int32
Ki_pri int32
Ki_rusage [144]byte
Ki_rusage_ch [144]byte
Ki_pcb int64
Ki_kstack int64
Ki_udata int64
Ki_tdaddr int64
Ki_spareptrs [48]byte
Ki_spareint64s [96]byte
Ki_sflag int64
Ki_tdflags int64
}
// UnixProcess is an implementation of Process that contains Unix-specific
// fields and information.
type UnixProcess struct {
pid int
ppid int
state rune
pgrp int
sid int
binary string
}
func (p *UnixProcess) Pid() int {
return p.pid
}
func (p *UnixProcess) PPid() int {
return p.ppid
}
func (p *UnixProcess) Executable() string {
return p.binary
}
// Refresh reloads all the data associated with this process.
func (p *UnixProcess) Refresh() error {
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PID, int32(p.pid)}
buf, length, err := call_syscall(mib)
if err != nil {
return err
}
proc_k := Kinfo_proc{}
if length != uint64(unsafe.Sizeof(proc_k)) {
return err
}
k, err := parse_kinfo_proc(buf)
if err != nil {
return err
}
p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k)
return nil
}
func copy_params(k *Kinfo_proc) (int, int, int, string) {
n := -1
for i, b := range k.Ki_comm {
if b == 0 {
break
}
n = i + 1
}
comm := string(k.Ki_comm[:n])
return int(k.Ki_ppid), int(k.Ki_pgid), int(k.Ki_sid), comm
}
func findProcess(pid int) (Process, error) {
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, int32(pid)}
_, _, err := call_syscall(mib)
if err != nil {
return nil, err
}
return newUnixProcess(pid)
}
func processes() ([]Process, error) {
results := make([]Process, 0, 50)
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0}
buf, length, err := call_syscall(mib)
if err != nil {
return results, err
}
// get kinfo_proc size
k := Kinfo_proc{}
procinfo_len := int(unsafe.Sizeof(k))
count := int(length / uint64(procinfo_len))
// parse buf to procs
for i := 0; i < count; i++ {
b := buf[i*procinfo_len : i*procinfo_len+procinfo_len]
k, err := parse_kinfo_proc(b)
if err != nil {
continue
}
p, err := newUnixProcess(int(k.Ki_pid))
if err != nil {
continue
}
p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k)
results = append(results, p)
}
return results, nil
}
func parse_kinfo_proc(buf []byte) (Kinfo_proc, error) {
var k Kinfo_proc
br := bytes.NewReader(buf)
err := binary.Read(br, binary.LittleEndian, &k)
if err != nil {
return k, err
}
return k, nil
}
func call_syscall(mib []int32) ([]byte, uint64, error) {
miblen := uint64(len(mib))
// get required buffer size
length := uint64(0)
_, _, err := syscall.RawSyscall6(
syscall.SYS___SYSCTL,
uintptr(unsafe.Pointer(&mib[0])),
uintptr(miblen),
0,
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
b := make([]byte, 0)
return b, length, err
}
if length == 0 {
b := make([]byte, 0)
return b, length, err
}
// get proc info itself
buf := make([]byte, length)
_, _, err = syscall.RawSyscall6(
syscall.SYS___SYSCTL,
uintptr(unsafe.Pointer(&mib[0])),
uintptr(miblen),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&length)),
0,
0)
if err != 0 {
return buf, length, err
}
return buf, length, nil
}
func newUnixProcess(pid int) (*UnixProcess, error) {
p := &UnixProcess{pid: pid}
return p, p.Refresh()
}

35
vendor/github.com/mitchellh/go-ps/process_linux.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
// +build linux
package ps
import (
"fmt"
"io/ioutil"
"strings"
)
// Refresh reloads all the data associated with this process.
func (p *UnixProcess) Refresh() error {
statPath := fmt.Sprintf("/proc/%d/stat", p.pid)
dataBytes, err := ioutil.ReadFile(statPath)
if err != nil {
return err
}
// First, parse out the image name
data := string(dataBytes)
binStart := strings.IndexRune(data, '(') + 1
binEnd := strings.IndexRune(data[binStart:], ')')
p.binary = data[binStart : binStart+binEnd]
// Move past the image name and start parsing the rest
data = data[binStart+binEnd+2:]
_, err = fmt.Sscanf(data,
"%c %d %d %d",
&p.state,
&p.ppid,
&p.pgrp,
&p.sid)
return err
}

96
vendor/github.com/mitchellh/go-ps/process_solaris.go generated vendored Normal file
View File

@@ -0,0 +1,96 @@
// +build solaris
package ps
import (
"encoding/binary"
"fmt"
"os"
)
type ushort_t uint16
type id_t int32
type pid_t int32
type uid_t int32
type gid_t int32
type dev_t uint64
type size_t uint64
type uintptr_t uint64
type timestruc_t [16]byte
// This is copy from /usr/include/sys/procfs.h
type psinfo_t struct {
Pr_flag int32 /* process flags (DEPRECATED; do not use) */
Pr_nlwp int32 /* number of active lwps in the process */
Pr_pid pid_t /* unique process id */
Pr_ppid pid_t /* process id of parent */
Pr_pgid pid_t /* pid of process group leader */
Pr_sid pid_t /* session id */
Pr_uid uid_t /* real user id */
Pr_euid uid_t /* effective user id */
Pr_gid gid_t /* real group id */
Pr_egid gid_t /* effective group id */
Pr_addr uintptr_t /* address of process */
Pr_size size_t /* size of process image in Kbytes */
Pr_rssize size_t /* resident set size in Kbytes */
Pr_pad1 size_t
Pr_ttydev dev_t /* controlling tty device (or PRNODEV) */
// Guess this following 2 ushort_t values require a padding to properly
// align to the 64bit mark.
Pr_pctcpu ushort_t /* % of recent cpu time used by all lwps */
Pr_pctmem ushort_t /* % of system memory used by process */
Pr_pad64bit [4]byte
Pr_start timestruc_t /* process start time, from the epoch */
Pr_time timestruc_t /* usr+sys cpu time for this process */
Pr_ctime timestruc_t /* usr+sys cpu time for reaped children */
Pr_fname [16]byte /* name of execed file */
Pr_psargs [80]byte /* initial characters of arg list */
Pr_wstat int32 /* if zombie, the wait() status */
Pr_argc int32 /* initial argument count */
Pr_argv uintptr_t /* address of initial argument vector */
Pr_envp uintptr_t /* address of initial environment vector */
Pr_dmodel [1]byte /* data model of the process */
Pr_pad2 [3]byte
Pr_taskid id_t /* task id */
Pr_projid id_t /* project id */
Pr_nzomb int32 /* number of zombie lwps in the process */
Pr_poolid id_t /* pool id */
Pr_zoneid id_t /* zone id */
Pr_contract id_t /* process contract */
Pr_filler int32 /* reserved for future use */
Pr_lwp [128]byte /* information for representative lwp */
}
func (p *UnixProcess) Refresh() error {
var psinfo psinfo_t
path := fmt.Sprintf("/proc/%d/psinfo", p.pid)
fh, err := os.Open(path)
if err != nil {
return err
}
defer fh.Close()
err = binary.Read(fh, binary.LittleEndian, &psinfo)
if err != nil {
return err
}
p.ppid = int(psinfo.Pr_ppid)
p.binary = toString(psinfo.Pr_fname[:], 16)
return nil
}
func toString(array []byte, len int) string {
for i := 0; i < len; i++ {
if array[i] == 0 {
return string(array[:i])
}
}
return string(array[:])
}

95
vendor/github.com/mitchellh/go-ps/process_unix.go generated vendored Normal file
View File

@@ -0,0 +1,95 @@
// +build linux solaris
package ps
import (
"fmt"
"io"
"os"
"strconv"
)
// UnixProcess is an implementation of Process that contains Unix-specific
// fields and information.
type UnixProcess struct {
pid int
ppid int
state rune
pgrp int
sid int
binary string
}
func (p *UnixProcess) Pid() int {
return p.pid
}
func (p *UnixProcess) PPid() int {
return p.ppid
}
func (p *UnixProcess) Executable() string {
return p.binary
}
func findProcess(pid int) (Process, error) {
dir := fmt.Sprintf("/proc/%d", pid)
_, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
return newUnixProcess(pid)
}
func processes() ([]Process, error) {
d, err := os.Open("/proc")
if err != nil {
return nil, err
}
defer d.Close()
results := make([]Process, 0, 50)
for {
names, err := d.Readdirnames(10)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
for _, name := range names {
// We only care if the name starts with a numeric
if name[0] < '0' || name[0] > '9' {
continue
}
// From this point forward, any errors we just ignore, because
// it might simply be that the process doesn't exist anymore.
pid, err := strconv.ParseInt(name, 10, 0)
if err != nil {
continue
}
p, err := newUnixProcess(int(pid))
if err != nil {
continue
}
results = append(results, p)
}
}
return results, nil
}
func newUnixProcess(pid int) (*UnixProcess, error) {
p := &UnixProcess{pid: pid}
return p, p.Refresh()
}

119
vendor/github.com/mitchellh/go-ps/process_windows.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
// +build windows
package ps
import (
"fmt"
"syscall"
"unsafe"
)
// Windows API functions
var (
modKernel32 = syscall.NewLazyDLL("kernel32.dll")
procCloseHandle = modKernel32.NewProc("CloseHandle")
procCreateToolhelp32Snapshot = modKernel32.NewProc("CreateToolhelp32Snapshot")
procProcess32First = modKernel32.NewProc("Process32FirstW")
procProcess32Next = modKernel32.NewProc("Process32NextW")
)
// Some constants from the Windows API
const (
ERROR_NO_MORE_FILES = 0x12
MAX_PATH = 260
)
// PROCESSENTRY32 is the Windows API structure that contains a process's
// information.
type PROCESSENTRY32 struct {
Size uint32
CntUsage uint32
ProcessID uint32
DefaultHeapID uintptr
ModuleID uint32
CntThreads uint32
ParentProcessID uint32
PriorityClassBase int32
Flags uint32
ExeFile [MAX_PATH]uint16
}
// WindowsProcess is an implementation of Process for Windows.
type WindowsProcess struct {
pid int
ppid int
exe string
}
func (p *WindowsProcess) Pid() int {
return p.pid
}
func (p *WindowsProcess) PPid() int {
return p.ppid
}
func (p *WindowsProcess) Executable() string {
return p.exe
}
func newWindowsProcess(e *PROCESSENTRY32) *WindowsProcess {
// Find when the string ends for decoding
end := 0
for {
if e.ExeFile[end] == 0 {
break
}
end++
}
return &WindowsProcess{
pid: int(e.ProcessID),
ppid: int(e.ParentProcessID),
exe: syscall.UTF16ToString(e.ExeFile[:end]),
}
}
func findProcess(pid int) (Process, error) {
ps, err := processes()
if err != nil {
return nil, err
}
for _, p := range ps {
if p.Pid() == pid {
return p, nil
}
}
return nil, nil
}
func processes() ([]Process, error) {
handle, _, _ := procCreateToolhelp32Snapshot.Call(
0x00000002,
0)
if handle < 0 {
return nil, syscall.GetLastError()
}
defer procCloseHandle.Call(handle)
var entry PROCESSENTRY32
entry.Size = uint32(unsafe.Sizeof(entry))
ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry)))
if ret == 0 {
return nil, fmt.Errorf("Error retrieving process info.")
}
results := make([]Process, 0, 50)
for {
results = append(results, newWindowsProcess(&entry))
ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry)))
if ret == 0 {
break
}
}
return results, nil
}

3
vendor/modules.txt generated vendored
View File

@@ -545,6 +545,9 @@ github.com/matttproud/golang_protobuf_extensions/pbutil
# github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
## explicit
github.com/mgutz/ansi
# github.com/mitchellh/go-ps v1.0.0
## explicit; go 1.13
github.com/mitchellh/go-ps
# github.com/mitchellh/go-wordwrap v1.0.0
## explicit
github.com/mitchellh/go-wordwrap