Compare commits

...

10 Commits

Author SHA1 Message Date
liruichen_wsl
e780c3f2ed rm: test workflow
All checks were successful
Build Release / release linux/amd64 (release) Successful in 1m51s
Build Release / release linux/386 (release) Successful in 1m20s
Build Release / release darwin/amd64 (release) Successful in 59s
Build Release / release windows/amd64 (release) Successful in 1m4s
2024-11-19 16:24:36 +08:00
liruichen_wsl
49c91795c6 Merge branch 'qiniu-v2' 2024-11-19 12:35:52 +08:00
liruichen_wsl
c170be7a00 merge: qiniu 2024-11-19 12:34:49 +08:00
liruichen_wsl
24e06870bb refactor: go module name 2024-11-19 11:32:10 +08:00
Li Yiyang
7b466bc7b4
feat: register extra information during build (#390)
* feat: register extra information during build

* update github action go version

* update action
2024-11-15 15:22:22 +08:00
liruichen
f0fbcc680a feat: only inject cmd 2024-09-05 15:10:18 +08:00
liruichen_wsl
9c8503082e feat: update go version 1.22 amend 2024-08-21 16:27:46 +08:00
liruichen_wsl
eddd55fdb5 feat: update go version 1.22 2024-08-21 16:21:59 +08:00
Li Yiyang
ba7372a236
support new go.mod (#378) 2024-06-18 11:25:45 +08:00
liruichen_wsl
c85c491bc9 style: fmtBranch 2024-03-13 16:24:27 +08:00
42 changed files with 2613 additions and 2648 deletions

37
.github/e2e-linux.yml vendored
View File

@ -1,37 +0,0 @@
name: e2e test
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
paths-ignore:
- '**.md'
- '**.png'
pull_request:
paths-ignore:
- '**.md'
- '**.png'
jobs:
job_1:
name: e2e test
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.16.x
- name: Checkout code
uses: actions/checkout@v2
- name: Go build
run: |
go build .
go install .
- name: Use goc to build self
run: |
./goc build -o ./gocc .
- name: run e2e test
run: |
go get github.com/onsi/ginkgo/ginkgo
make e2e

37
.github/e2e-wins.yml vendored
View File

@ -1,37 +0,0 @@
name: e2e test
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
paths-ignore:
- '**.md'
- '**.png'
pull_request:
paths-ignore:
- '**.md'
- '**.png'
jobs:
job_1:
name: e2e test
strategy:
matrix:
os: [windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.16.x
- name: Checkout code
uses: actions/checkout@v2
- name: Go build
run: |
go build .
go install .
- name: Use goc to build self
run: |
.\goc.exe build -o gocc .
- name: run e2e test
run: |
go get github.com/onsi/ginkgo/ginkgo
ginkgo tests/e2e/...

View File

@ -1,39 +0,0 @@
name: style-check
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
paths-ignore:
- '**.md'
- '**.png'
pull_request:
paths-ignore:
- '**.md'
- '**.png'
jobs:
run:
name: vet and gofmt
strategy:
matrix:
go-version: [1.16.x]
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
# This step checks out a copy of your repository.
- name: Checkout code
uses: actions/checkout@v2
- name: Go vet check
run: |
go vet ./...
- name: Gofmt check
run: |
diff=`find . -name "*.go" | xargs gofmt -s -d`
if [[ -n "${diff}" ]]; then
echo "Gofmt check failed :"
echo "${diff}"
echo "Please run this command to fix: [find . -name "*.go" | xargs gofmt -s -w]"
exit 1
fi

View File

@ -1,32 +0,0 @@
name: unit test
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
paths-ignore:
- '**.md'
- '**.png'
pull_request:
paths-ignore:
- '**.md'
- '**.png'
jobs:
run:
name: go test on windows
strategy:
matrix:
go-version: [1.16.x]
runs-on: windows-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
# This step checks out a copy of your repository.
- name: Checkout code
uses: actions/checkout@v2
- name: Go test
env:
GOVERSION: ${{ matrix.go-version }}
run: |
go test -p 1 .\pkg\...

33
.github/ut-check.yml vendored
View File

@ -1,33 +0,0 @@
name: unit test
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
paths-ignore:
- '**.md'
- '**.png'
pull_request:
paths-ignore:
- '**.md'
- '**.png'
jobs:
run:
name: go test on linux
strategy:
matrix:
go-version: [1.16.x]
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
# This step checks out a copy of your repository.
- name: Checkout code
uses: actions/checkout@v2
- name: Go test
env:
GOVERSION: ${{ matrix.go-version }}
run: |
make test
bash <(curl -s https://codecov.io/bash) -F unittest-$GOVERSION

View File

@ -12,7 +12,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.19.x go-version: 1.22.x
- name: compile and release - name: compile and release
run: | run: |
./hack/release.sh ./hack/release.sh
@ -30,7 +30,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.19.x go-version: 1.22.x
- name: compile and release - name: compile and release
run: | run: |
./hack/release.sh ./hack/release.sh
@ -48,7 +48,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.19.x go-version: 1.22.x
- name: compile and release - name: compile and release
run: | run: |
./hack/release.sh ./hack/release.sh
@ -57,19 +57,19 @@ jobs:
GOARCH: amd64 GOARCH: amd64
GOOS: darwin GOOS: darwin
# release-windows-amd64: release-windows-amd64:
# name: release windows/amd64 name: release windows/amd64
# runs-on: ubuntu-latest runs-on: ubuntu-latest
# steps: steps:
# - uses: actions/checkout@master - uses: actions/checkout@master
# - name: Install Go - name: Install Go
# uses: actions/setup-go@v2 uses: actions/setup-go@v2
# with: with:
# go-version: 1.19.x go-version: 1.22.x
# - name: compile and release - name: compile and release
# run: | run: |
# ./hack/release.sh ./hack/release.sh
# env: env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# GOARCH: amd64 GOARCH: amd64
# GOOS: windows GOOS: windows

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
tests/e2e/tmp/* tests/e2e/tmp/*
.goc.kvstore .goc.kvstore
coverage.txt coverage.txt
.idea
repos

View File

@ -14,40 +14,43 @@
package cmd package cmd
import ( import (
"github.com/RickLeee/goc/v2/pkg/build" "github.com/ar0c/goc/v2/pkg/build"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var buildCmd = &cobra.Command{ var buildCmd = &cobra.Command{
Use: "build", Use: "build",
Run: buildAction, Run: buildAction,
DisableFlagParsing: true, // build 命令需要用原生 go 的方式处理 flags DisableFlagParsing: true, // build 命令需要用原生 go 的方式处理 flags
} }
var ( var (
gocmode string gocmode string
gochost string gochost string
gocextra string
) )
func init() { func init() {
buildCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch") buildCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch")
buildCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever") buildCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever")
rootCmd.AddCommand(buildCmd) buildCmd.Flags().StringVarP(&gocextra, "gocextra", "", "", "specify the extra information injected into the build")
rootCmd.AddCommand(buildCmd)
} }
func buildAction(cmd *cobra.Command, args []string) { func buildAction(cmd *cobra.Command, args []string) {
sets := build.CustomParseCmdAndArgs(cmd, args) sets := build.CustomParseCmdAndArgs(cmd, args)
b := build.NewBuild( b := build.NewBuild(
build.WithHost(gochost), build.WithHost(gochost),
build.WithMode(gocmode), build.WithMode(gocmode),
build.WithFlagSets(sets), build.WithFlagSets(sets),
build.WithArgs(args), build.WithArgs(args),
build.WithBuild(), build.WithBuild(),
build.WithDebug(globalDebug), build.WithDebug(globalDebug),
) build.WithExtra(gocextra),
b.Build() )
b.Build()
} }

44
cmd/inject.go Normal file
View File

@ -0,0 +1,44 @@
/*
Copyright 2021 Qiniu Cloud (qiniu.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"github.com/ar0c/goc/v2/pkg/build"
"github.com/spf13/cobra"
)
var injectCmd = &cobra.Command{
Use: "inject",
Run: injectAction,
//DisableFlagParsing: true, // build 命令需要用原生 go 的方式处理 flags
}
func init() {
injectCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch")
injectCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever")
rootCmd.AddCommand(injectCmd)
}
func injectAction(cmd *cobra.Command, args []string) {
b := build.NewInject(
build.WithHost(gochost),
build.WithMode(gocmode),
build.WithArgs(args),
build.WithDebug(globalDebug),
)
b.OnlyInject()
}

View File

@ -14,35 +14,37 @@
package cmd package cmd
import ( import (
"github.com/RickLeee/goc/v2/pkg/build" "github.com/ar0c/goc/v2/pkg/build"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var installCmd = &cobra.Command{ var installCmd = &cobra.Command{
Use: "install", Use: "install",
Run: installAction, Run: installAction,
DisableFlagParsing: true, // install 命令需要用原生 go 的方式处理 flags DisableFlagParsing: true, // install 命令需要用原生 go 的方式处理 flags
} }
func init() { func init() {
installCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch") installCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch")
installCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever") installCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever")
rootCmd.AddCommand(installCmd) installCmd.Flags().StringVarP(&gocextra, "gocextra", "", "", "specify the extra information injected into the build")
rootCmd.AddCommand(installCmd)
} }
func installAction(cmd *cobra.Command, args []string) { func installAction(cmd *cobra.Command, args []string) {
sets := build.CustomParseCmdAndArgs(cmd, args) sets := build.CustomParseCmdAndArgs(cmd, args)
b := build.NewInstall( b := build.NewInstall(
build.WithHost(gochost), build.WithHost(gochost),
build.WithMode(gocmode), build.WithMode(gocmode),
build.WithFlagSets(sets), build.WithFlagSets(sets),
build.WithArgs(args), build.WithArgs(args),
build.WithInstall(), build.WithInstall(),
build.WithDebug(globalDebug), build.WithDebug(globalDebug),
) build.WithExtra(gocextra),
b.Install() )
b.Install()
} }

View File

@ -14,55 +14,55 @@
package cmd package cmd
import ( import (
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/tools/cover" "golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov" "k8s.io/test-infra/gopherage/pkg/cov"
"k8s.io/test-infra/gopherage/pkg/util" "k8s.io/test-infra/gopherage/pkg/util"
) )
var mergeCmd = &cobra.Command{ var mergeCmd = &cobra.Command{
Use: "merge [files...]", Use: "merge [files...]",
Short: "Merge multiple coherent Go coverage files into a single file.", Short: "Merge multiple coherent Go coverage files into a single file.",
Long: `Merge will merge multiple Go coverage files into a single coverage file. Long: `Merge will merge multiple Go coverage files into a single coverage file.
merge requires that the files are 'coherent', meaning that if they both contain references to the merge requires that the files are 'coherent', meaning that if they both contain references to the
same paths, then the contents of those source files were identical for the binary that generated same paths, then the contents of those source files were identical for the binary that generated
each file. each file.
`, `,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
runMerge(args, outputMergeProfile) runMerge(args, outputMergeProfile)
}, },
} }
var outputMergeProfile string var outputMergeProfile string
func init() { func init() {
mergeCmd.Flags().StringVarP(&outputMergeProfile, "output", "o", "mergeprofile.cov", "output file") mergeCmd.Flags().StringVarP(&outputMergeProfile, "output", "o", "mergeprofile.cov", "output file")
rootCmd.AddCommand(mergeCmd) rootCmd.AddCommand(mergeCmd)
} }
func runMerge(args []string, output string) { func runMerge(args []string, output string) {
if len(args) == 0 { if len(args) == 0 {
log.Fatalf("Expected at least one coverage file.") log.Fatalf("Expected at least one coverage file.")
} }
profiles := make([][]*cover.Profile, len(args)) profiles := make([][]*cover.Profile, len(args))
for _, path := range args { for _, path := range args {
profile, err := util.LoadProfile(path) profile, err := util.LoadProfile(path)
if err != nil { if err != nil {
log.Fatalf("failed to open %s: %v", path, err) log.Fatalf("failed to open %s: %v", path, err)
} }
profiles = append(profiles, profile) profiles = append(profiles, profile)
} }
merged, err := cov.MergeMultipleProfiles(profiles) merged, err := cov.MergeMultipleProfiles(profiles)
if err != nil { if err != nil {
log.Fatalf("failed to merge files: %v", err) log.Fatalf("failed to merge files: %v", err)
} }
err = util.DumpProfile(output, merged) err = util.DumpProfile(output, merged)
if err != nil { if err != nil {
log.Fatalf("fail to dump the merged file: %v", err) log.Fatalf("fail to dump the merged file: %v", err)
} }
} }

View File

@ -14,65 +14,65 @@
package cmd package cmd
import ( import (
"github.com/RickLeee/goc/v2/pkg/client" "github.com/ar0c/goc/v2/pkg/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
var profileCmd = &cobra.Command{ var profileCmd = &cobra.Command{
Use: "profile", Use: "profile",
Short: "Get coverage profile from service registry center", Short: "Get coverage profile from service registry center",
Long: `Get code coverage profile for the services under test at runtime.`, Long: `Get code coverage profile for the services under test at runtime.`,
//Run: profile, //Run: profile,
} }
var ( var (
profileHost string profileHost string
profileOutput string // --output flag profileOutput string // --output flag
profileIds []string profileIds []string
profileSkipPattern []string profileSkipPattern []string
profileExtra string profileExtra string
profileNeedPattern []string profileNeedPattern []string
) )
func init() { func init() {
add1Flags := func(f *pflag.FlagSet) { add1Flags := func(f *pflag.FlagSet) {
f.StringVar(&profileHost, "host", "127.0.0.1:7777", "specify the host of the goc server") f.StringVar(&profileHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
f.StringSliceVar(&profileIds, "id", nil, "specify the ids of the services") f.StringSliceVar(&profileIds, "id", nil, "specify the ids of the services")
f.StringVar(&profileExtra, "extra", "", "specify the regex expression of extra, only profile with extra information will be downloaded") f.StringVar(&profileExtra, "extra", "", "specify the regex expression of extra, only profile with extra information will be downloaded")
} }
add2Flags := func(f *pflag.FlagSet) { add2Flags := func(f *pflag.FlagSet) {
f.StringVarP(&profileOutput, "output", "o", "", "download cover profile") f.StringVarP(&profileOutput, "output", "o", "", "download cover profile")
f.StringSliceVar(&profileSkipPattern, "skip", nil, "skip specific packages in the profile") f.StringSliceVar(&profileSkipPattern, "skip", nil, "skip specific packages in the profile")
f.StringSliceVarP(&profileNeedPattern, "need", "n", nil, "find specific packages in the profile") f.StringSliceVarP(&profileNeedPattern, "need", "n", nil, "find specific packages in the profile")
} }
add1Flags(getProfileCmd.Flags()) add1Flags(getProfileCmd.Flags())
add2Flags(getProfileCmd.Flags()) add2Flags(getProfileCmd.Flags())
add1Flags(clearProfileCmd.Flags()) add1Flags(clearProfileCmd.Flags())
profileCmd.AddCommand(getProfileCmd) profileCmd.AddCommand(getProfileCmd)
profileCmd.AddCommand(clearProfileCmd) profileCmd.AddCommand(clearProfileCmd)
rootCmd.AddCommand(profileCmd) rootCmd.AddCommand(profileCmd)
} }
var getProfileCmd = &cobra.Command{ var getProfileCmd = &cobra.Command{
Use: "get", Use: "get",
Run: getProfile, Run: getProfile,
} }
func getProfile(cmd *cobra.Command, args []string) { func getProfile(cmd *cobra.Command, args []string) {
client.GetProfile(profileHost, profileIds, profileSkipPattern, profileExtra, profileOutput, profileNeedPattern) client.GetProfile(profileHost, profileIds, profileSkipPattern, profileExtra, profileOutput, profileNeedPattern)
} }
var clearProfileCmd = &cobra.Command{ var clearProfileCmd = &cobra.Command{
Use: "clear", Use: "clear",
Run: clearProfile, Run: clearProfile,
} }
func clearProfile(cmd *cobra.Command, args []string) { func clearProfile(cmd *cobra.Command, args []string) {
client.ClearProfile(profileHost, profileIds, profileExtra) client.ClearProfile(profileHost, profileIds, profileExtra)
} }

View File

@ -14,38 +14,38 @@
package cmd package cmd
import ( import (
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "goc", Use: "goc",
Short: "goc is a comprehensive coverage testing tool for go language", Short: "goc is a comprehensive coverage testing tool for go language",
Long: `goc is a comprehensive coverage testing tool for go language. Long: `goc is a comprehensive coverage testing tool for go language.
Find more information at: Find more information at:
https://github.com/qiniu/goc https://github.com/qiniu/goc
`, `,
PersistentPreRun: func(cmd *cobra.Command, args []string) { PersistentPreRun: func(cmd *cobra.Command, args []string) {
//log.DisplayGoc() //log.DisplayGoc()
// init logger // init logger
log.NewLogger(globalDebug) log.NewLogger(globalDebug)
}, },
PersistentPostRun: func(cmd *cobra.Command, args []string) { PersistentPostRun: func(cmd *cobra.Command, args []string) {
log.Sync() log.Sync()
}, },
} }
var globalDebug bool var globalDebug bool
func init() { func init() {
rootCmd.PersistentFlags().BoolVar(&globalDebug, "gocdebug", false, "run goc in debug mode") rootCmd.PersistentFlags().BoolVar(&globalDebug, "gocdebug", false, "run goc in debug mode")
} }
// Execute the goc tool // Execute the goc tool
func Execute() { func Execute() {
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
} }
} }

View File

@ -14,34 +14,34 @@
package cmd package cmd
import ( import (
"github.com/RickLeee/goc/v2/pkg/build" "github.com/ar0c/goc/v2/pkg/build"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var runCmd = &cobra.Command{ var runCmd = &cobra.Command{
Use: "run", Use: "run",
Run: runAction, Run: runAction,
DisableFlagParsing: true, // run 命令需要用原生 go 的方式处理 flags DisableFlagParsing: true, // run 命令需要用原生 go 的方式处理 flags
} }
func init() { func init() {
runCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch") runCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch")
runCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever") runCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever")
rootCmd.AddCommand(runCmd) rootCmd.AddCommand(runCmd)
} }
func runAction(cmd *cobra.Command, args []string) { func runAction(cmd *cobra.Command, args []string) {
sets := build.CustomParseCmdAndArgs(cmd, args) sets := build.CustomParseCmdAndArgs(cmd, args)
b := build.NewRun( b := build.NewRun(
build.WithHost(gochost), build.WithHost(gochost),
build.WithMode(gocmode), build.WithMode(gocmode),
build.WithFlagSets(sets), build.WithFlagSets(sets),
build.WithArgs(args), build.WithArgs(args),
build.WithBuild(), build.WithBuild(),
build.WithDebug(globalDebug), build.WithDebug(globalDebug),
) )
b.Run() b.Run()
} }

View File

@ -14,36 +14,36 @@
package cmd package cmd
import ( import (
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/server" "github.com/ar0c/goc/v2/pkg/server"
"github.com/RickLeee/goc/v2/pkg/server/store" "github.com/ar0c/goc/v2/pkg/server/store"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var serverCmd = &cobra.Command{ var serverCmd = &cobra.Command{
Use: "server", Use: "server",
Short: "Start a service registry center", Short: "Start a service registry center",
Example: "", Example: "",
Run: serve, Run: serve,
} }
var ( var (
serverHost string serverHost string
serverStore string serverStore string
) )
func init() { func init() {
serverCmd.Flags().StringVarP(&serverHost, "host", "", "127.0.0.1:7777", "specify the host of the goc server") serverCmd.Flags().StringVarP(&serverHost, "host", "", "127.0.0.1:7777", "specify the host of the goc server")
serverCmd.Flags().StringVarP(&serverStore, "store", "", ".goc.kvstore", "specify the host of the goc server") serverCmd.Flags().StringVarP(&serverStore, "store", "", ".goc.kvstore", "specify the host of the goc server")
rootCmd.AddCommand(serverCmd) rootCmd.AddCommand(serverCmd)
} }
func serve(cmd *cobra.Command, args []string) { func serve(cmd *cobra.Command, args []string) {
s, err := store.NewFileStore(serverStore) s, err := store.NewFileStore(serverStore)
if err != nil { if err != nil {
log.Fatalf("cannot create store for goc server: %v", err) log.Fatalf("cannot create store for goc server: %v", err)
} }
server.RunGocServerUntilExit(serverHost, s) server.RunGocServerUntilExit(serverHost, s)
} }

30
cmd/server_test.go Normal file
View File

@ -0,0 +1,30 @@
package cmd
import (
"github.com/ar0c/goc/v2/pkg/log"
"github.com/spf13/cobra"
"testing"
)
func Test_serve(t *testing.T) {
type args struct {
cmd *cobra.Command
args []string
}
tests := []struct {
name string
args args
}{
{
name: "",
args: args{},
},
// TODO: Add test cases.
}
for _, tt := range tests {
log.NewLogger(true)
t.Run(tt.name, func(t *testing.T) {
serve(tt.args.cmd, tt.args.args)
})
}
}

View File

@ -14,61 +14,61 @@
package cmd package cmd
import ( import (
"github.com/RickLeee/goc/v2/pkg/client" "github.com/ar0c/goc/v2/pkg/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
var listCmd = &cobra.Command{ var listCmd = &cobra.Command{
Use: "service", Use: "service",
Short: "Deal with the registered services", Short: "Deal with the registered services",
Long: `It can be used to list, remove the registered services. Long: `It can be used to list, remove the registered services.
For disconnected services, remove will delete these serivces forever, For disconnected services, remove will delete these serivces forever,
for connected services remove will force these services register again.`, for connected services remove will force these services register again.`,
} }
var ( var (
listHost string listHost string
listWide bool listWide bool
listIds []string listIds []string
listJson bool listJson bool
) )
func init() { func init() {
add1Flags := func(f *pflag.FlagSet) { add1Flags := func(f *pflag.FlagSet) {
f.StringVar(&listHost, "host", "127.0.0.1:7777", "specify the host of the goc server") f.StringVar(&listHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
f.BoolVar(&listWide, "wide", false, "list all services with more information (such as pid)") f.BoolVar(&listWide, "wide", false, "list all services with more information (such as pid)")
f.BoolVar(&listJson, "json", false, "list all services info as json format") f.BoolVar(&listJson, "json", false, "list all services info as json format")
f.StringSliceVar(&listIds, "id", nil, "specify the ids of the services") f.StringSliceVar(&listIds, "id", nil, "specify the ids of the services")
} }
add1Flags(getServiceCmd.Flags()) add1Flags(getServiceCmd.Flags())
add1Flags(deleteServiceCmd.Flags()) add1Flags(deleteServiceCmd.Flags())
listCmd.AddCommand(getServiceCmd) listCmd.AddCommand(getServiceCmd)
listCmd.AddCommand(deleteServiceCmd) listCmd.AddCommand(deleteServiceCmd)
rootCmd.AddCommand(listCmd) rootCmd.AddCommand(listCmd)
} }
func list(cmd *cobra.Command, args []string) { func list(cmd *cobra.Command, args []string) {
client.ListAgents(listHost, listIds, listWide, listJson) client.ListAgents(listHost, listIds, listWide, listJson)
} }
var getServiceCmd = &cobra.Command{ var getServiceCmd = &cobra.Command{
Use: "get", Use: "get",
Run: getAgents, Run: getAgents,
} }
func getAgents(cmd *cobra.Command, args []string) { func getAgents(cmd *cobra.Command, args []string) {
client.ListAgents(listHost, listIds, listWide, listJson) client.ListAgents(listHost, listIds, listWide, listJson)
} }
var deleteServiceCmd = &cobra.Command{ var deleteServiceCmd = &cobra.Command{
Use: "delete", Use: "delete",
Run: deleteAgents, Run: deleteAgents,
} }
func deleteAgents(cmd *cobra.Command, args []string) { func deleteAgents(cmd *cobra.Command, args []string) {
client.DeleteAgents(listHost, listIds) client.DeleteAgents(listHost, listIds)
} }

View File

@ -14,28 +14,28 @@
package cmd package cmd
import ( import (
cli "github.com/RickLeee/goc/v2/pkg/watch" cli "github.com/ar0c/goc/v2/pkg/watch"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var watchCmd = &cobra.Command{ var watchCmd = &cobra.Command{
Use: "watch", Use: "watch",
Short: "watch for profile's real time update", Short: "watch for profile's real time update",
Long: "watch for profile's real time update", Long: "watch for profile's real time update",
Example: "", Example: "",
Run: watch, Run: watch,
} }
var ( var (
watchHost string watchHost string
) )
func init() { func init() {
watchCmd.Flags().StringVarP(&watchHost, "host", "", "127.0.0.1:7777", "specify the host of the goc server") watchCmd.Flags().StringVarP(&watchHost, "host", "", "127.0.0.1:7777", "specify the host of the goc server")
rootCmd.AddCommand(watchCmd) rootCmd.AddCommand(watchCmd)
} }
func watch(cmd *cobra.Command, args []string) { func watch(cmd *cobra.Command, args []string) {
cli.Watch(watchHost) cli.Watch(watchHost)
} }

21
go.mod
View File

@ -1,6 +1,8 @@
module github.com/RickLeee/goc/v2 module github.com/ar0c/goc/v2
go 1.20 go 1.22.0
toolchain go1.22.5
require ( require (
github.com/gin-gonic/gin v1.7.2 github.com/gin-gonic/gin v1.7.2
@ -17,9 +19,9 @@ require (
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/tongjingran/copy v1.4.2 github.com/tongjingran/copy v1.4.2
go.uber.org/zap v1.17.0 go.uber.org/zap v1.17.0
golang.org/x/mod v0.4.2 golang.org/x/mod v0.21.0
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d golang.org/x/term v0.12.0
golang.org/x/tools v0.1.3 golang.org/x/tools v0.13.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
k8s.io/kubectl v0.21.2 k8s.io/kubectl v0.21.2
k8s.io/test-infra v0.0.0-20210618100605-34aa2f2aa75b k8s.io/test-infra v0.0.0-20210618100605-34aa2f2aa75b
@ -54,13 +56,12 @@ require (
github.com/ugorji/go/codec v1.1.7 // indirect github.com/ugorji/go/codec v1.1.7 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect golang.org/x/crypto v0.13.0 // indirect
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect golang.org/x/net v0.15.0 // indirect
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.3.6 // indirect golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.26.0 // indirect google.golang.org/protobuf v1.26.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect

24
go.sum
View File

@ -1320,8 +1320,9 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1360,8 +1361,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1417,8 +1418,9 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -1524,12 +1526,13 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1538,8 +1541,9 @@ golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5f
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1648,8 +1652,8 @@ golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4X
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -10,7 +10,7 @@ RELEASE_VERSION=$(echo $EVENT_DATA | jq -r .release.tag_name)
PROJECT_NAME=$(basename $GITHUB_REPOSITORY) PROJECT_NAME=$(basename $GITHUB_REPOSITORY)
NAME="${NAME:-${PROJECT_NAME}-${RELEASE_VERSION}}-${GOOS}-${GOARCH}" NAME="${NAME:-${PROJECT_NAME}-${RELEASE_VERSION}}-${GOOS}-${GOARCH}"
CGO_ENABLED=0 go build -o goc -ldflags "-X 'github.com/RickLeee/goc/v2/cmd.Version=${RELEASE_VERSION}'" . CGO_ENABLED=0 go build -o goc -ldflags "-X 'github.com/ar0c/goc/v2/cmd.Version=${RELEASE_VERSION}'" .
ARCHIVE=tmp.tar.gz ARCHIVE=tmp.tar.gz
FILE_LIST=goc FILE_LIST=goc

View File

@ -14,9 +14,9 @@
package main package main
import ( import (
"github.com/RickLeee/goc/v2/cmd" "github.com/ar0c/goc/v2/cmd"
) )
func main() { func main() {
cmd.Execute() cmd.Execute()
} }

View File

@ -45,9 +45,9 @@ var (
token string token string
id string id string
cond = sync.NewCond(&sync.Mutex{}) cond = sync.NewCond(&sync.Mutex{})
register_extra = ""
commitID string = "{{.CommitID}}" commitID string = "{{.CommitID}}"
branch string = "{{.Branch}}" branch string = "{{.Branch}}"
register_extra = "{{.Extra}}"
) )
func init() { func init() {
@ -57,6 +57,11 @@ func init() {
host = host_env host = host_env
} }
// init extra information
if os.Getenv("GOC_REGISTER_EXTRA") != "" {
register_extra = os.Getenv("GOC_REGISTER_EXTRA")
}
var dialer = websocket.DefaultDialer var dialer = websocket.DefaultDialer
go func() { go func() {

View File

@ -14,62 +14,87 @@
package build package build
import ( import (
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
// Build struct a build // Build struct a build
type Build struct { type Build struct {
Args []string // all goc + go command line args + flags Args []string // all goc + go command line args + flags
FlagSets *pflag.FlagSet FlagSets *pflag.FlagSet
BuildType int BuildType int
Debug bool Debug bool
Host string Host string
Mode string // cover mode Mode string // cover mode
Extra string
GOPATH string GOPATH string
GOBIN string GOBIN string
CurWd string CurWd string
TmpWd string TmpWd string
CurModProjectDir string CurModProjectDir string
TmpModProjectDir string TmpModProjectDir string
Goflags []string // go command line flags Goflags []string // go command line flags
GoArgs []string // go command line args GoArgs []string // go command line args
Packages []string // go command line [packages] Packages []string // go command line [packages]
IsVendorMod bool // vendor, readonly, or mod? IsVendorMod bool // vendor, readonly, or mod?
IsModEdit bool // is mod file edited? IsModEdit bool // is mod file edited?
ImportPath string // the whole import path of the project ImportPath string // the whole import path of the project
Pkgs map[string]*Package Pkgs map[string]*Package
GlobalCoverVarImportPath string GlobalCoverVarImportPath string
GlobalCoverVarImportPathDir string GlobalCoverVarImportPathDir string
} }
// NewBuild creates a Build struct // NewBuild creates a Build struct
func NewBuild(opts ...gocOption) *Build { func NewBuild(opts ...gocOption) *Build {
b := &Build{} b := &Build{}
for _, opt := range opts { for _, opt := range opts {
opt(b) opt(b)
} }
// 1. 解析 goc 命令行和 go 命令行 // 1. 解析 goc 命令行和 go 命令行
b.buildCmdArgsParse() b.buildCmdArgsParse()
// 2. 解析 go 包位置 // 2. 解析 go 包位置
b.getPackagesDir() b.getPackagesDir()
// 3. 读取工程元信息go.mod, pkgs list ... // 3. 读取工程元信息go.mod, pkgs list ...
b.readProjectMetaInfo() b.readProjectMetaInfo()
// 4. 展示元信息 // 4. 展示元信息
b.displayProjectMetaInfo() b.displayProjectMetaInfo()
return b return b
}
func NewInject(opts ...gocOption) *Build {
b := &Build{}
for _, opt := range opts {
opt(b)
}
curWd, err := os.Getwd()
if err != nil {
log.Fatalf("fail to get current working directory: %v", err)
}
b.CurWd = curWd
//// 1. 解析 goc 命令行和 go 命令行
//b.buildCmdArgsParse()
// 2. 解析 go 包位置
b.getPackagesDir()
// 3. 读取工程元信息go.mod, pkgs list ...
b.readProjectMetaInfo(true)
// 4. 展示元信息
b.displayProjectMetaInfo()
return b
} }
// Build starts go build // Build starts go build
@ -78,69 +103,81 @@ func NewBuild(opts ...gocOption) *Build {
// 2. inject cover variables and functions into the project, // 2. inject cover variables and functions into the project,
// 3. build the project in temp. // 3. build the project in temp.
func (b *Build) Build() { func (b *Build) Build() {
// 1. 拷贝至临时目录 // 1. 拷贝至临时目录
b.copyProjectToTmp() b.copyProjectToTmp()
defer b.clean() defer b.clean()
log.Donef("project copied to temporary directory") log.Donef("project copied to temporary directory")
// 2. update go.mod file if needed // 2. update go.mod file if needed
b.updateGoModFile() b.updateGoModFile()
// 3. inject cover vars // 3. inject cover vars
b.Inject() b.Inject()
if b.IsVendorMod && b.IsModEdit { if b.IsVendorMod && b.IsModEdit {
b.reVendor() b.reVendor()
} }
// 4. build in the temp project // 4. build in the temp project
b.doBuildInTemp() b.doBuildInTemp()
}
func (b *Build) OnlyInject() {
// 2. update go.mod file if needed
b.updateGoModFile()
// 3. inject cover vars
b.Inject()
if b.IsVendorMod && b.IsModEdit {
b.reVendor()
}
} }
func (b *Build) doBuildInTemp() { func (b *Build) doBuildInTemp() {
log.StartWait("building the injected project") log.StartWait("building the injected project")
goflags := b.Goflags goflags := b.Goflags
// 检查用户是否自定义了 -o // 检查用户是否自定义了 -o
oSet := false oSet := false
for _, flag := range goflags { for _, flag := range goflags {
if flag == "-o" { if flag == "-o" {
oSet = true oSet = true
} }
} }
// 如果没被设置就加一个至原命令执行的目录 // 如果没被设置就加一个至原命令执行的目录
if !oSet { if !oSet {
goflags = append(goflags, "-o", b.CurWd) goflags = append(goflags, "-o", b.CurWd)
} }
if b.IsVendorMod && b.IsModEdit { if b.IsVendorMod && b.IsModEdit {
b.reVendor() b.reVendor()
} }
pacakges := b.Packages pacakges := b.Packages
goflags = append(goflags, pacakges...) goflags = append(goflags, pacakges...)
args := []string{"build"} args := []string{"build"}
args = append(args, goflags...) args = append(args, goflags...)
// go 命令行由 go build [-o output] [build flags] [packages] 组成 // go 命令行由 go build [-o output] [build flags] [packages] 组成
cmd := exec.Command("go", args...) cmd := exec.Command("go", args...)
cmd.Dir = b.TmpWd cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
log.Infof("go build cmd is: %v, in path [%v]", nicePrintArgs(cmd.Args), cmd.Dir) log.Infof("go build cmd is: %v, in path [%v]", nicePrintArgs(cmd.Args), cmd.Dir)
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
log.Fatalf("fail to execute go build: %v", err) log.Fatalf("fail to execute go build: %v", err)
} }
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
log.Fatalf("fail to execute go build: %v", err) log.Fatalf("fail to execute go build: %v", err)
} }
// done // done
log.StopWait() log.StopWait()
log.Donef("go build done") log.Donef("go build done")
} }
// nicePrintArgs 优化 args 打印内容 // nicePrintArgs 优化 args 打印内容
@ -149,32 +186,32 @@ func (b *Build) doBuildInTemp() {
// //
// 实际输出会变为go build -ldflags -X my/package/config.Version=1.0.0 -o /home/lyy/gitdown/gin-test/cmd . // 实际输出会变为go build -ldflags -X my/package/config.Version=1.0.0 -o /home/lyy/gitdown/gin-test/cmd .
func nicePrintArgs(args []string) []string { func nicePrintArgs(args []string) []string {
output := make([]string, 0) output := make([]string, 0)
for _, arg := range args { for _, arg := range args {
if strings.Contains(arg, " ") { if strings.Contains(arg, " ") {
output = append(output, "\""+arg+"\"") output = append(output, "\""+arg+"\"")
} else { } else {
output = append(output, arg) output = append(output, arg)
} }
} }
return output return output
} }
func (b *Build) reVendor() { func (b *Build) reVendor() {
log.StartWait("re-vendoring the project") log.StartWait("re-vendoring the project")
cmd := exec.Command("go", "mod", "vendor") cmd := exec.Command("go", "mod", "vendor")
cmd.Dir = b.TmpModProjectDir cmd.Dir = b.TmpModProjectDir
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
log.Fatalf("fail to execute go vendor: %v", err) log.Fatalf("fail to execute go vendor: %v", err)
} }
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
log.Fatalf("fail to execute go vendor: %v", err) log.Fatalf("fail to execute go vendor: %v", err)
} }
log.StopWait() log.StopWait()
log.Donef("re-vendor the project done") log.Donef("re-vendor the project done")
} }

View File

@ -14,15 +14,15 @@
package build package build
import ( import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
var buildUsage string = `Usage: var buildUsage string = `Usage:
@ -53,23 +53,23 @@ However, other flags' order are same with the go official command.
` `
const ( const (
GO_BUILD = iota GO_BUILD = iota
GO_INSTALL GO_INSTALL
) )
// CustomParseCmdAndArgs 因为关闭了 cobra 的解析功能,需要手动构造并解析 goc flags // CustomParseCmdAndArgs 因为关闭了 cobra 的解析功能,需要手动构造并解析 goc flags
func CustomParseCmdAndArgs(cmd *cobra.Command, args []string) *pflag.FlagSet { func CustomParseCmdAndArgs(cmd *cobra.Command, args []string) *pflag.FlagSet {
// 首先解析 cobra 定义的 flag // 首先解析 cobra 定义的 flag
allFlagSets := cmd.Flags() allFlagSets := cmd.Flags()
// 因为 args 里面含有 go 的 flag所以需要忽略解析 go flag 的错误 // 因为 args 里面含有 go 的 flag所以需要忽略解析 go flag 的错误
allFlagSets.Init("GOC", pflag.ContinueOnError) allFlagSets.Init("GOC", pflag.ContinueOnError)
// 忽略 go flag 在 goc 中的解析错误 // 忽略 go flag 在 goc 中的解析错误
allFlagSets.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{ allFlagSets.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{
UnknownFlags: true, UnknownFlags: true,
} }
allFlagSets.Parse(args) allFlagSets.Parse(args)
return allFlagSets return allFlagSets
} }
// buildCmdArgsParse parse both go flags and goc flags, it rewrite go flags if // buildCmdArgsParse parse both go flags and goc flags, it rewrite go flags if
@ -77,246 +77,246 @@ func CustomParseCmdAndArgs(cmd *cobra.Command, args []string) *pflag.FlagSet {
// //
// 吞下 [packages] 之前所有的 flags. // 吞下 [packages] 之前所有的 flags.
func (b *Build) buildCmdArgsParse() { func (b *Build) buildCmdArgsParse() {
args := b.Args args := b.Args
cmdType := b.BuildType cmdType := b.BuildType
allFlagSets := b.FlagSets allFlagSets := b.FlagSets
// 重写 help // 重写 help
helpFlag := allFlagSets.Lookup("help") helpFlag := allFlagSets.Lookup("help")
if helpFlag.Changed { if helpFlag.Changed {
if cmdType == GO_BUILD { if cmdType == GO_BUILD {
printGoHelp(buildUsage) printGoHelp(buildUsage)
} else if cmdType == GO_INSTALL { } else if cmdType == GO_INSTALL {
printGoHelp(installUsage) printGoHelp(installUsage)
} }
os.Exit(0) os.Exit(0)
} }
// 删除 help flag // 删除 help flag
args = findAndDelHelpFlag(args) args = findAndDelHelpFlag(args)
// 必须手动调用 // 必须手动调用
// 由于关闭了 cobra 的 flag parseroot PersistentPreRun 调用时log.NewLogger 并没有拿到 debug 值 // 由于关闭了 cobra 的 flag parseroot PersistentPreRun 调用时log.NewLogger 并没有拿到 debug 值
log.NewLogger(b.Debug) log.NewLogger(b.Debug)
// 删除 cobra 定义的 flag // 删除 cobra 定义的 flag
allFlagSets.Visit(func(f *pflag.Flag) { allFlagSets.Visit(func(f *pflag.Flag) {
args = findAndDelGocFlag(args, f.Name, f.Value.String()) args = findAndDelGocFlag(args, f.Name, f.Value.String())
}) })
// 然后解析 go 的 flag // 然后解析 go 的 flag
goFlagSets := flag.NewFlagSet("GO", flag.ContinueOnError) goFlagSets := flag.NewFlagSet("GO", flag.ContinueOnError)
addBuildFlags(goFlagSets) addBuildFlags(goFlagSets)
addOutputFlags(goFlagSets) addOutputFlags(goFlagSets)
err := goFlagSets.Parse(args) err := goFlagSets.Parse(args)
if err != nil { if err != nil {
log.Fatalf("%v", err) log.Fatalf("%v", err)
} }
// 找出设置的 go flag // 找出设置的 go flag
curWd, err := os.Getwd() curWd, err := os.Getwd()
if err != nil { if err != nil {
log.Fatalf("fail to get current working directory: %v", err) log.Fatalf("fail to get current working directory: %v", err)
} }
flags := make([]string, 0) flags := make([]string, 0)
goFlagSets.Visit(func(f *flag.Flag) { goFlagSets.Visit(func(f *flag.Flag) {
// 将用户指定 -o 改成绝对目录 // 将用户指定 -o 改成绝对目录
if f.Name == "o" { if f.Name == "o" {
outputDir := f.Value.String() outputDir := f.Value.String()
outputDir, err := filepath.Abs(outputDir) outputDir, err := filepath.Abs(outputDir)
if err != nil { if err != nil {
log.Fatalf("output flag is not valid: %v", err) log.Fatalf("output flag is not valid: %v", err)
} }
flags = append(flags, "-o", outputDir) flags = append(flags, "-o", outputDir)
} else { } else {
if _, ok := booleanFlags[f.Name]; !ok { if _, ok := booleanFlags[f.Name]; !ok {
flags = append(flags, "-"+f.Name, f.Value.String()) flags = append(flags, "-"+f.Name, f.Value.String())
} else { } else {
flags = append(flags, "-"+f.Name) flags = append(flags, "-"+f.Name)
} }
if f.Name == "mod" { if f.Name == "mod" {
if f.Value.String() == "vendor" { if f.Value.String() == "vendor" {
b.IsVendorMod = true b.IsVendorMod = true
} else { } else {
b.IsVendorMod = false b.IsVendorMod = false
} }
} }
} }
}) })
b.Goflags = flags b.Goflags = flags
b.CurWd = curWd b.CurWd = curWd
b.GoArgs = goFlagSets.Args() b.GoArgs = goFlagSets.Args()
return return
} }
func (b *Build) runCmdArgsParse() { func (b *Build) runCmdArgsParse() {
args := b.Args args := b.Args
allFlagSets := b.FlagSets allFlagSets := b.FlagSets
// 重写 help // 重写 help
helpFlag := allFlagSets.Lookup("help") helpFlag := allFlagSets.Lookup("help")
if helpFlag.Changed { if helpFlag.Changed {
printGoHelp(runUsage) printGoHelp(runUsage)
os.Exit(0) os.Exit(0)
} }
// 删除 help flag // 删除 help flag
args = findAndDelHelpFlag(args) args = findAndDelHelpFlag(args)
// 必须手动调用 // 必须手动调用
// 由于关闭了 cobra 的 flag parseroot PersistentPreRun 调用时log.NewLogger 并没有拿到 debug 值 // 由于关闭了 cobra 的 flag parseroot PersistentPreRun 调用时log.NewLogger 并没有拿到 debug 值
log.NewLogger(b.Debug) log.NewLogger(b.Debug)
curWd, err := os.Getwd() curWd, err := os.Getwd()
if err != nil { if err != nil {
log.Fatalf("fail to get current working directory: %v", err) log.Fatalf("fail to get current working directory: %v", err)
} }
b.CurWd = curWd b.CurWd = curWd
// 获取除 goc flags 之外的 args // 获取除 goc flags 之外的 args
// 删除 cobra 定义的 flag // 删除 cobra 定义的 flag
allFlagSets.Visit(func(f *pflag.Flag) { allFlagSets.Visit(func(f *pflag.Flag) {
args = findAndDelGocFlag(args, f.Name, f.Value.String()) args = findAndDelGocFlag(args, f.Name, f.Value.String())
}) })
b.GoArgs = args b.GoArgs = args
} }
func findAndDelGocFlag(a []string, x string, v string) []string { func findAndDelGocFlag(a []string, x string, v string) []string {
new := make([]string, 0, len(a)) new := make([]string, 0, len(a))
x = "--" + x x = "--" + x
x_v := x + "=" + v x_v := x + "=" + v
for i := 0; i < len(a); i++ { for i := 0; i < len(a); i++ {
if a[i] == "--gocdebug" { if a[i] == "--gocdebug" {
// debug 是 bool就一个元素 // debug 是 bool就一个元素
continue continue
} else if a[i] == x { } else if a[i] == x {
// 有 goc flag 长这样 --mode watch // 有 goc flag 长这样 --mode watch
i++ i++
continue continue
} else if a[i] == x_v { } else if a[i] == x_v {
// 有 goc flag 长这样 --mode=watch // 有 goc flag 长这样 --mode=watch
continue continue
} else { } else {
// 剩下的是 go flag // 剩下的是 go flag
new = append(new, a[i]) new = append(new, a[i])
} }
} }
return new return new
} }
func findAndDelHelpFlag(a []string) []string { func findAndDelHelpFlag(a []string) []string {
new := make([]string, 0, len(a)) new := make([]string, 0, len(a))
for _, v := range a { for _, v := range a {
if v == "--help" || v == "-h" { if v == "--help" || v == "-h" {
continue continue
} else { } else {
new = append(new, v) new = append(new, v)
} }
} }
return new return new
} }
type goConfig struct { type goConfig struct {
BuildA bool BuildA bool
BuildBuildmode string // -buildmode flag BuildBuildmode string // -buildmode flag
BuildMod string // -mod flag BuildMod string // -mod flag
BuildModReason string // reason -mod flag is set, if set by default BuildModReason string // reason -mod flag is set, if set by default
BuildI bool // -i flag BuildI bool // -i flag
BuildLinkshared bool // -linkshared flag BuildLinkshared bool // -linkshared flag
BuildMSan bool // -msan flag BuildMSan bool // -msan flag
BuildN bool // -n flag BuildN bool // -n flag
BuildO string // -o flag BuildO string // -o flag
BuildP int // -p flag BuildP int // -p flag
BuildPkgdir string // -pkgdir flag BuildPkgdir string // -pkgdir flag
BuildRace bool // -race flag BuildRace bool // -race flag
BuildToolexec string // -toolexec flag BuildToolexec string // -toolexec flag
BuildToolchainName string BuildToolchainName string
BuildToolchainCompiler func() string BuildToolchainCompiler func() string
BuildToolchainLinker func() string BuildToolchainLinker func() string
BuildTrimpath bool // -trimpath flag BuildTrimpath bool // -trimpath flag
BuildV bool // -v flag BuildV bool // -v flag
BuildWork bool // -work flag BuildWork bool // -work flag
BuildX bool // -x flag BuildX bool // -x flag
// from buildcontext // from buildcontext
Installsuffix string // -installSuffix Installsuffix string // -installSuffix
BuildTags string // -tags BuildTags string // -tags
// from load // from load
BuildAsmflags string BuildAsmflags string
BuildCompiler string BuildCompiler string
BuildGcflags string BuildGcflags string
BuildGccgoflags string BuildGccgoflags string
BuildLdflags string BuildLdflags string
// mod related // mod related
ModCacheRW bool ModCacheRW bool
ModFile string ModFile string
} }
var goflags goConfig var goflags goConfig
var booleanFlags map[string]struct{} = make(map[string]struct{}) var booleanFlags map[string]struct{} = make(map[string]struct{})
func addBuildFlags(cmdSet *flag.FlagSet) { func addBuildFlags(cmdSet *flag.FlagSet) {
cmdSet.BoolVar(&goflags.BuildA, "a", false, "") cmdSet.BoolVar(&goflags.BuildA, "a", false, "")
booleanFlags["a"] = struct{}{} booleanFlags["a"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildN, "n", false, "") cmdSet.BoolVar(&goflags.BuildN, "n", false, "")
booleanFlags["n"] = struct{}{} booleanFlags["n"] = struct{}{}
cmdSet.IntVar(&goflags.BuildP, "p", 4, "") cmdSet.IntVar(&goflags.BuildP, "p", 4, "")
cmdSet.BoolVar(&goflags.BuildV, "v", false, "") cmdSet.BoolVar(&goflags.BuildV, "v", false, "")
booleanFlags["v"] = struct{}{} booleanFlags["v"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildX, "x", false, "") cmdSet.BoolVar(&goflags.BuildX, "x", false, "")
booleanFlags["x"] = struct{}{} booleanFlags["x"] = struct{}{}
cmdSet.StringVar(&goflags.BuildBuildmode, "buildmode", "default", "") cmdSet.StringVar(&goflags.BuildBuildmode, "buildmode", "default", "")
cmdSet.StringVar(&goflags.BuildMod, "mod", "", "") cmdSet.StringVar(&goflags.BuildMod, "mod", "", "")
cmdSet.StringVar(&goflags.Installsuffix, "installsuffix", "", "") cmdSet.StringVar(&goflags.Installsuffix, "installsuffix", "", "")
// 类型和 go 原生的不一样,这里纯粹是为了 parse 并传递给 go // 类型和 go 原生的不一样,这里纯粹是为了 parse 并传递给 go
cmdSet.StringVar(&goflags.BuildAsmflags, "asmflags", "", "") cmdSet.StringVar(&goflags.BuildAsmflags, "asmflags", "", "")
cmdSet.StringVar(&goflags.BuildCompiler, "compiler", "", "") cmdSet.StringVar(&goflags.BuildCompiler, "compiler", "", "")
cmdSet.StringVar(&goflags.BuildGcflags, "gcflags", "", "") cmdSet.StringVar(&goflags.BuildGcflags, "gcflags", "", "")
cmdSet.StringVar(&goflags.BuildGccgoflags, "gccgoflags", "", "") cmdSet.StringVar(&goflags.BuildGccgoflags, "gccgoflags", "", "")
// mod related // mod related
cmdSet.BoolVar(&goflags.ModCacheRW, "modcacherw", false, "") cmdSet.BoolVar(&goflags.ModCacheRW, "modcacherw", false, "")
booleanFlags["modcacherw"] = struct{}{} booleanFlags["modcacherw"] = struct{}{}
cmdSet.StringVar(&goflags.ModFile, "modfile", "", "") cmdSet.StringVar(&goflags.ModFile, "modfile", "", "")
cmdSet.StringVar(&goflags.BuildLdflags, "ldflags", "", "") cmdSet.StringVar(&goflags.BuildLdflags, "ldflags", "", "")
cmdSet.BoolVar(&goflags.BuildLinkshared, "linkshared", false, "") cmdSet.BoolVar(&goflags.BuildLinkshared, "linkshared", false, "")
booleanFlags["linkshared"] = struct{}{} booleanFlags["linkshared"] = struct{}{}
cmdSet.StringVar(&goflags.BuildPkgdir, "pkgdir", "", "") cmdSet.StringVar(&goflags.BuildPkgdir, "pkgdir", "", "")
cmdSet.BoolVar(&goflags.BuildRace, "race", false, "") cmdSet.BoolVar(&goflags.BuildRace, "race", false, "")
booleanFlags["race"] = struct{}{} booleanFlags["race"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildMSan, "msan", false, "") cmdSet.BoolVar(&goflags.BuildMSan, "msan", false, "")
booleanFlags["msan"] = struct{}{} booleanFlags["msan"] = struct{}{}
cmdSet.StringVar(&goflags.BuildTags, "tags", "", "") cmdSet.StringVar(&goflags.BuildTags, "tags", "", "")
cmdSet.StringVar(&goflags.BuildToolexec, "toolexec", "", "") cmdSet.StringVar(&goflags.BuildToolexec, "toolexec", "", "")
cmdSet.BoolVar(&goflags.BuildTrimpath, "trimpath", false, "") cmdSet.BoolVar(&goflags.BuildTrimpath, "trimpath", false, "")
booleanFlags["trimpath"] = struct{}{} booleanFlags["trimpath"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildWork, "work", false, "") cmdSet.BoolVar(&goflags.BuildWork, "work", false, "")
booleanFlags["work"] = struct{}{} booleanFlags["work"] = struct{}{}
} }
func addOutputFlags(cmdSet *flag.FlagSet) { func addOutputFlags(cmdSet *flag.FlagSet) {
cmdSet.StringVar(&goflags.BuildO, "o", "", "") cmdSet.StringVar(&goflags.BuildO, "o", "", "")
} }
func printGoHelp(usage string) { func printGoHelp(usage string) {
fmt.Println(usage) fmt.Println(usage)
} }
func printGocHelp(cmd *cobra.Command) { func printGocHelp(cmd *cobra.Command) {
flags := cmd.LocalFlags() flags := cmd.LocalFlags()
globalFlags := cmd.Parent().PersistentFlags() globalFlags := cmd.Parent().PersistentFlags()
fmt.Println("Flags:") fmt.Println("Flags:")
fmt.Println(flags.FlagUsages()) fmt.Println(flags.FlagUsages())
fmt.Println("Global Flags:") fmt.Println("Global Flags:")
fmt.Println(globalFlags.FlagUsages()) fmt.Println(globalFlags.FlagUsages())
} }
// GetPackagesDir parse [pacakges] part of args, it will fatal if error encountered // GetPackagesDir parse [pacakges] part of args, it will fatal if error encountered
@ -328,43 +328,43 @@ func printGocHelp(cmd *cobra.Command) {
// 如果 [packages] 非法(即不符合 go 原生的定义),则返回对应错误 // 如果 [packages] 非法(即不符合 go 原生的定义),则返回对应错误
// 这里只考虑 go mod 的方式 // 这里只考虑 go mod 的方式
func (b *Build) getPackagesDir() { func (b *Build) getPackagesDir() {
patterns := b.GoArgs patterns := b.GoArgs
packages := make([]string, 0) packages := make([]string, 0)
for _, p := range patterns { for _, p := range patterns {
// patterns 只支持两种格式 // patterns 只支持两种格式
// 1. 要么是直接指向某些 .go 文件的相对/绝对路径 // 1. 要么是直接指向某些 .go 文件的相对/绝对路径
if strings.HasSuffix(p, ".go") { if strings.HasSuffix(p, ".go") {
if fi, err := os.Stat(p); err == nil && !fi.IsDir() { if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
// check if valid // check if valid
if err := goFilesPackage(patterns); err != nil { if err := goFilesPackage(patterns); err != nil {
log.Fatalf("%v", err) log.Fatalf("%v", err)
} }
// 获取相对于 current working directory 对路径 // 获取相对于 current working directory 对路径
for _, p := range patterns { for _, p := range patterns {
if filepath.IsAbs(p) { if filepath.IsAbs(p) {
relPath, err := filepath.Rel(b.CurWd, p) relPath, err := filepath.Rel(b.CurWd, p)
if err != nil { if err != nil {
log.Fatalf("fail to get [packages] relative path from current working directory: %v", err) log.Fatalf("fail to get [packages] relative path from current working directory: %v", err)
} }
packages = append(packages, relPath) packages = append(packages, relPath)
} else { } else {
packages = append(packages, p) packages = append(packages, p)
} }
} }
// fix: go build ./xx/main.go 需要转换为 // fix: go build ./xx/main.go 需要转换为
// go build ./xx/main.go ./xx/goc-cover-agent-apis-auto-generated-11111-22222-bridge.go // go build ./xx/main.go ./xx/goc-cover-agent-apis-auto-generated-11111-22222-bridge.go
dir := filepath.Dir(packages[0]) dir := filepath.Dir(packages[0])
packages = append(packages, filepath.Join(dir, "goc-cover-agent-apis-auto-generated-11111-22222-bridge.go")) packages = append(packages, filepath.Join(dir, "goc-cover-agent-apis-auto-generated-11111-22222-bridge.go"))
b.Packages = packages b.Packages = packages
return return
} }
} }
} }
// 2. 要么是 import path // 2. 要么是 import path
b.Packages = patterns b.Packages = patterns
} }
// goFilesPackage 对一组 go 文件解析,判断是否合法 // goFilesPackage 对一组 go 文件解析,判断是否合法
@ -373,40 +373,40 @@ func (b *Build) getPackagesDir() {
// 2. *.go 文件都在同一个目录? // 2. *.go 文件都在同一个目录?
// 3. *.go 文件存在? // 3. *.go 文件存在?
func goFilesPackage(gofiles []string) error { func goFilesPackage(gofiles []string) error {
// 1. 必须都是 *.go 结尾 // 1. 必须都是 *.go 结尾
for _, f := range gofiles { for _, f := range gofiles {
if !strings.HasSuffix(f, ".go") { if !strings.HasSuffix(f, ".go") {
return fmt.Errorf("named files must be .go files: %s", f) return fmt.Errorf("named files must be .go files: %s", f)
} }
} }
var dir string var dir string
for _, file := range gofiles { for _, file := range gofiles {
// 3. 文件都存在? // 3. 文件都存在?
fi, err := os.Stat(file) fi, err := os.Stat(file)
if err != nil { if err != nil {
return err return err
} }
// 2.1 有可能以 *.go 结尾的目录 // 2.1 有可能以 *.go 结尾的目录
if fi.IsDir() { if fi.IsDir() {
return fmt.Errorf("%s is a directory, should be a Go file", file) return fmt.Errorf("%s is a directory, should be a Go file", file)
} }
// 2.2 所有 *.go 必须在同一个目录内 // 2.2 所有 *.go 必须在同一个目录内
dir1, _ := filepath.Split(file) dir1, _ := filepath.Split(file)
if dir1 == "" { if dir1 == "" {
dir1 = "./" dir1 = "./"
} }
if dir == "" { if dir == "" {
dir = dir1 dir = dir1
} else if dir != dir1 { } else if dir != dir1 {
return fmt.Errorf("named files must all be in one directory: have %s and %s", dir, dir1) return fmt.Errorf("named files must all be in one directory: have %s and %s", dir, dir1)
} }
} }
return nil return nil
} }
// getDirFromImportPaths return the import path's real abs directory // getDirFromImportPaths return the import path's real abs directory
@ -414,90 +414,90 @@ func goFilesPackage(gofiles []string) error {
// 该函数接收到的只有 dir 或 import pathfile 在上一步已被排除 // 该函数接收到的只有 dir 或 import pathfile 在上一步已被排除
// 只考虑 go modules 的情况 // 只考虑 go modules 的情况
func getDirFromImportPaths(patterns []string) (string, error) { func getDirFromImportPaths(patterns []string) (string, error) {
// no import path, pattern = current wd // no import path, pattern = current wd
if len(patterns) == 0 { if len(patterns) == 0 {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
return "", fmt.Errorf("fail to parse import path: %w", err) return "", fmt.Errorf("fail to parse import path: %w", err)
} }
return wd, nil return wd, nil
} }
// 为了简化插桩的逻辑goc 对 import path 要求必须都在同一个目录 // 为了简化插桩的逻辑goc 对 import path 要求必须都在同一个目录
// 所以干脆只允许一个 pattern 得了 -_- // 所以干脆只允许一个 pattern 得了 -_-
// 对于 goc build/run 来说本身就是只能在一个目录内 // 对于 goc build/run 来说本身就是只能在一个目录内
// 对于 goc install 来讲,这个行为就和 go install 不同,不过多 import path 较少见 >_<,先忽略 // 对于 goc install 来讲,这个行为就和 go install 不同,不过多 import path 较少见 >_<,先忽略
if len(patterns) > 1 { if len(patterns) > 1 {
return "", fmt.Errorf("goc only support one import path now") return "", fmt.Errorf("goc only support one import path now")
} }
pattern := patterns[0] pattern := patterns[0]
switch { switch {
// case isLocalImport(pattern) || filepath.IsAbs(pattern): // case isLocalImport(pattern) || filepath.IsAbs(pattern):
// dir1, err := filepath.Abs(pattern) // dir1, err := filepath.Abs(pattern)
// if err != nil { // if err != nil {
// return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern) // return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
// } // }
// if _, err := os.Stat(dir1); err != nil { // if _, err := os.Stat(dir1); err != nil {
// return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern) // return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
// } // }
// return dir1, nil // return dir1, nil
case strings.Contains(pattern, "..."): case strings.Contains(pattern, "..."):
i := strings.Index(pattern, "...") i := strings.Index(pattern, "...")
dir, _ := filepath.Split(pattern[:i]) dir, _ := filepath.Split(pattern[:i])
dir, _ = filepath.Abs(dir) dir, _ = filepath.Abs(dir)
if _, err := os.Stat(dir); err != nil { if _, err := os.Stat(dir); err != nil {
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern) return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
} }
return dir, nil return dir, nil
case strings.IndexByte(pattern, '@') > 0: case strings.IndexByte(pattern, '@') > 0:
return "", fmt.Errorf("import path with @ version query is not supported in goc") return "", fmt.Errorf("import path with @ version query is not supported in goc")
case isMetaPackage(pattern): case isMetaPackage(pattern):
return "", fmt.Errorf("`std`, `cmd`, `all` import path is not supported by goc") return "", fmt.Errorf("`std`, `cmd`, `all` import path is not supported by goc")
default: // 到这一步认为 pattern 是相对路径或者绝对路径 default: // 到这一步认为 pattern 是相对路径或者绝对路径
dir1, err := filepath.Abs(pattern) dir1, err := filepath.Abs(pattern)
if err != nil { if err != nil {
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern) return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
} }
if _, err := os.Stat(dir1); err != nil { if _, err := os.Stat(dir1); err != nil {
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern) return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
} }
return dir1, nil return dir1, nil
} }
} }
// isLocalImport reports whether the import path is // isLocalImport reports whether the import path is
// a local import path, like ".", "..", "./foo", or "../foo" // a local import path, like ".", "..", "./foo", or "../foo"
func isLocalImport(path string) bool { func isLocalImport(path string) bool {
return path == "." || path == ".." || return path == "." || path == ".." ||
strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
} }
// isMetaPackage checks if the name is a reserved package name // isMetaPackage checks if the name is a reserved package name
func isMetaPackage(name string) bool { func isMetaPackage(name string) bool {
return name == "std" || name == "cmd" || name == "all" return name == "std" || name == "cmd" || name == "all"
} }
// find direct path of current project which contains go.mod // find direct path of current project which contains go.mod
func findModuleRoot(dir string) string { func findModuleRoot(dir string) string {
dir = filepath.Clean(dir) dir = filepath.Clean(dir)
// look for enclosing go.mod // look for enclosing go.mod
for { for {
if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() { if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
return dir return dir
} }
d := filepath.Dir(dir) d := filepath.Dir(dir)
if d == dir { if d == dir {
break break
} }
dir = d dir = d
} }
return "" return ""
} }

View File

@ -14,6 +14,8 @@
package build package build
import ( import (
"strings"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
@ -60,3 +62,9 @@ func WithDebug(enable bool) gocOption {
b.Debug = enable b.Debug = enable
} }
} }
func WithExtra(extra string) gocOption {
return func(b *Build) {
b.Extra = strings.TrimSpace(extra)
}
}

View File

@ -14,141 +14,143 @@
package build package build
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
) )
// readProjectMetaInfo reads all meta informations of the corresponding project // readProjectMetaInfo reads all meta informations of the corresponding project
func (b *Build) readProjectMetaInfo() { func (b *Build) readProjectMetaInfo(noTemp ...bool) {
// get gopath & gobin // get gopath & gobin
b.GOPATH = b.readGOPATH() b.GOPATH = b.readGOPATH()
b.GOBIN = b.readGOBIN() b.GOBIN = b.readGOBIN()
// 获取 [packages] 及其依赖的 package list // 获取 [packages] 及其依赖的 package list
pkgs := b.listPackages(b.CurWd) pkgs := b.listPackages(b.CurWd)
// get mod info // get mod info
for _, pkg := range pkgs { for _, pkg := range pkgs {
// check if go modules is enabled // check if go modules is enabled
if pkg.Module == nil { if pkg.Module == nil {
log.Fatalf("Go module is not enabled, please set GO111MODULE=auto or on") log.Fatalf("Go module is not enabled, please set GO111MODULE=auto or on")
} }
// 工程根目录 // 工程根目录
b.CurModProjectDir = pkg.Module.Dir b.CurModProjectDir = pkg.Module.Dir
b.ImportPath = pkg.Module.Path b.ImportPath = pkg.Module.Path
break break
} }
// 如果当前目录不是工程根目录,那再次 go list 一次,获取整个工程的包信息 // 如果当前目录不是工程根目录,那再次 go list 一次,获取整个工程的包信息
if b.CurWd != b.CurModProjectDir { if b.CurWd != b.CurModProjectDir {
b.Pkgs = b.listPackages(b.CurModProjectDir) b.Pkgs = b.listPackages(b.CurModProjectDir)
} else { } else {
b.Pkgs = pkgs b.Pkgs = pkgs
} }
// check if project is in vendor mod // check if project is in vendor mod
b.checkIfVendorMod() b.checkIfVendorMod()
// get tmp folder name // get tmp folder name
b.TmpModProjectDir = filepath.Join(os.TempDir(), TmpFolderName(b.CurModProjectDir)) b.TmpModProjectDir = filepath.Join(os.TempDir(), TmpFolderName(b.CurModProjectDir))
// get working dir in the corresponding tmp dir if len(noTemp) != 0 && noTemp[0] {
b.TmpWd = filepath.Join(b.TmpModProjectDir, b.CurWd[len(b.CurModProjectDir):]) b.TmpModProjectDir = b.CurModProjectDir
// get GlobalCoverVarImportPath }
b.GlobalCoverVarImportPath = path.Join(b.ImportPath, TmpFolderName(b.CurModProjectDir)) // get working dir in the corresponding tmp dir
log.Donef("project meta information parsed") b.TmpWd = filepath.Join(b.TmpModProjectDir, b.CurWd[len(b.CurModProjectDir):])
// get GlobalCoverVarImportPath
b.GlobalCoverVarImportPath = path.Join(b.ImportPath, TmpFolderName(b.CurModProjectDir))
log.Donef("project meta information parsed")
} }
// displayProjectMetaInfo prints basic infomation of this project to stdout // displayProjectMetaInfo prints basic infomation of this project to stdout
func (b *Build) displayProjectMetaInfo() { func (b *Build) displayProjectMetaInfo() {
log.Infof("GOPATH: %v", b.GOPATH) log.Infof("GOPATH: %v", b.GOPATH)
log.Infof("GOBIN: %v", b.GOBIN) log.Infof("GOBIN: %v", b.GOBIN)
log.Infof("Project Directory: %v", b.CurModProjectDir) log.Infof("Project Directory: %v", b.CurModProjectDir)
log.Infof("GOC_REGISTER_EXTRA from env: %v", os.Getenv("GOC_REGISTER_EXTRA")) log.Infof("Temporary Project Directory: %v", b.TmpModProjectDir)
log.Infof("Temporary Project Directory: %v", b.TmpModProjectDir) if b.IsVendorMod {
if b.IsVendorMod { log.Infof("Project in vendor mod")
log.Infof("Project in vendor mod") }
} log.Infof("")
log.Infof("")
} }
// readGOPATH reads GOPATH use go env GOPATH command // readGOPATH reads GOPATH use go env GOPATH command
func (b *Build) readGOPATH() string { func (b *Build) readGOPATH() string {
out, err := exec.Command("go", "env", "GOPATH").Output() out, err := exec.Command("go", "env", "GOPATH").Output()
if err != nil { if err != nil {
log.Fatalf("fail to read GOPATH: %v", err) log.Fatalf("fail to read GOPATH: %v", err)
} }
return strings.TrimSpace(string(out)) return strings.TrimSpace(string(out))
} }
// readGOBIN reads GOBIN use go env GOBIN command // readGOBIN reads GOBIN use go env GOBIN command
func (b *Build) readGOBIN() string { func (b *Build) readGOBIN() string {
out, err := exec.Command("go", "env", "GOBIN").Output() out, err := exec.Command("go", "env", "GOBIN").Output()
if err != nil { if err != nil {
log.Fatalf("fail to read GOBIN: %v", err) log.Fatalf("fail to read GOBIN: %v", err)
} }
return strings.TrimSpace(string(out)) return strings.TrimSpace(string(out))
} }
// listPackages list all packages under specific via go list command. // listPackages list all packages under specific via go list command.
func (b *Build) listPackages(dir string) map[string]*Package { func (b *Build) listPackages(dir string) map[string]*Package {
listArgs := []string{"list", "-json"} listArgs := []string{"list", "-json"}
if goflags.BuildTags != "" { if goflags.BuildTags != "" {
listArgs = append(listArgs, "-tags", goflags.BuildTags) listArgs = append(listArgs, "-tags", goflags.BuildTags)
} }
listArgs = append(listArgs, "./...") listArgs = append(listArgs, "./...")
cmd := exec.Command("go", listArgs...) cmd := exec.Command("go", listArgs...)
cmd.Dir = dir cmd.Dir = dir
var errBuf bytes.Buffer var errBuf bytes.Buffer
cmd.Stderr = &errBuf cmd.Stderr = &errBuf
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {
log.Fatalf("execute go list -json failed, err: %v, stdout: %v, stderr: %v", err, string(out), errBuf.String()) log.Fatalf("execute go list -json failed, err: %v, stdout: %v, stderr: %v", err, string(out), errBuf.String())
} }
// 有些时候 go 命令会打印一些信息到 stderr但其实命令整体是成功运行了 // 有些时候 go 命令会打印一些信息到 stderr但其实命令整体是成功运行了
if errBuf.String() != "" { if errBuf.String() != "" {
log.Errorf("%v", errBuf.String()) log.Errorf("%v", errBuf.String())
} }
dec := json.NewDecoder(bytes.NewBuffer(out)) dec := json.NewDecoder(bytes.NewBuffer(out))
pkgs := make(map[string]*Package) pkgs := make(map[string]*Package)
for { for {
var pkg Package var pkg Package
if err := dec.Decode(&pkg); err != nil { if err := dec.Decode(&pkg); err != nil {
if err == io.EOF { if err == io.EOF {
break break
} }
log.Fatalf("reading go list output error: %v", err) log.Fatalf("reading go list output error: %v", err)
} }
if pkg.Error != nil { if pkg.Error != nil {
log.Fatalf("list package %s failed with output: %v", pkg.ImportPath, pkg.Error) log.Fatalf("list package %s failed with output: %v", pkg.ImportPath, pkg.Error)
} }
pkgs[pkg.ImportPath] = &pkg pkgs[pkg.ImportPath] = &pkg
} }
return pkgs return pkgs
} }
func (b *Build) checkIfVendorMod() { func (b *Build) checkIfVendorMod() {
if b.IsVendorMod == true { if b.IsVendorMod == true {
return return
} }
vendorDir := filepath.Join(b.CurModProjectDir, "vendor") vendorDir := filepath.Join(b.CurModProjectDir, "vendor")
if _, err := os.Stat(vendorDir); err != nil { if _, err := os.Stat(vendorDir); err != nil {
b.IsVendorMod = false b.IsVendorMod = false
} }
b.IsVendorMod = true b.IsVendorMod = true
} }

View File

@ -21,9 +21,9 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/RickLeee/goc/v2/pkg/build/internal/tool" "github.com/ar0c/goc/v2/pkg/build/internal/tool"
"github.com/RickLeee/goc/v2/pkg/build/internal/websocket" "github.com/ar0c/goc/v2/pkg/build/internal/websocket"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
) )
// Inject injects cover variables for all the .go files in the target directory // Inject injects cover variables for all the .go files in the target directory
@ -195,7 +195,7 @@ func (b *Build) injectGocAgent(where string, covers []*PackageCover) {
cmd := exec.Command("git", "rev-parse", "--short=8", "HEAD") cmd := exec.Command("git", "rev-parse", "--short=8", "HEAD")
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
log.Errorf("git describe Error: %v", err) log.Errorf("git rev-parse Error: %v", err)
} else { } else {
commitID = strings.TrimRight(string(output), "\n") commitID = strings.TrimRight(string(output), "\n")
} }
@ -205,12 +205,7 @@ func (b *Build) injectGocAgent(where string, covers []*PackageCover) {
if err != nil { if err != nil {
log.Errorf("get git branch Error: %v", err) log.Errorf("get git branch Error: %v", err)
} else { } else {
log.Infof("[goc][info] raw branch: %v ", br) branch = fmtBranch(br)
branch = strings.Replace(string(br), "\n", "", -1)
branch = strings.TrimLeft(branch, "heads/")
branch = strings.Trim(branch, " ")
branch = strings.Replace(branch, "/", "-", -1)
branch = strings.Replace(branch, "_", "-", -1)
} }
log.Infof("[goc][info] branch: %v --- commitID: %v", branch, commitID) log.Infof("[goc][info] branch: %v --- commitID: %v", branch, commitID)
tmplData := struct { tmplData := struct {
@ -221,6 +216,7 @@ func (b *Build) injectGocAgent(where string, covers []*PackageCover) {
Mode string Mode string
CommitID string CommitID string
Branch string Branch string
Extra string
}{ }{
Covers: covers, Covers: covers,
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath, GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
@ -229,6 +225,7 @@ func (b *Build) injectGocAgent(where string, covers []*PackageCover) {
Mode: _coverMode, Mode: _coverMode,
Branch: branch, Branch: branch,
CommitID: commitID, CommitID: commitID,
Extra: b.Extra,
} }
if err := coverMainTmpl.Execute(f2, tmplData); err != nil { if err := coverMainTmpl.Execute(f2, tmplData); err != nil {
@ -317,3 +314,11 @@ func UploadCoverChangeEvent_%v(name string, pos []uint32, i int, stmts uint16) {
log.Fatalf("fail to write to global cover definition file: %v", err) log.Fatalf("fail to write to global cover definition file: %v", err)
} }
} }
func fmtBranch(br []byte) (branch string) {
branch = strings.Replace(string(br), "\n", "", -1)
branch = strings.TrimLeft(branch, "heads/")
branch = strings.Trim(branch, " ")
branch = strings.Replace(branch, "/", "-", -1)
return
}

View File

@ -14,14 +14,14 @@
package build package build
import ( import (
"os" "os"
"os/exec" "os/exec"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
) )
func NewInstall(opts ...gocOption) *Build { func NewInstall(opts ...gocOption) *Build {
return NewBuild(opts...) return NewBuild(opts...)
} }
// Install starts go install // Install starts go install
@ -30,51 +30,51 @@ func NewInstall(opts ...gocOption) *Build {
// 2. inject cover variables and functions into the project, // 2. inject cover variables and functions into the project,
// 3. install the project in temp. // 3. install the project in temp.
func (b *Build) Install() { func (b *Build) Install() {
// 1. 拷贝至临时目录 // 1. 拷贝至临时目录
b.copyProjectToTmp() b.copyProjectToTmp()
defer b.clean() defer b.clean()
log.Donef("project copied to temporary directory") log.Donef("project copied to temporary directory")
// 2. update go.mod file if needed // 2. update go.mod file if needed
b.updateGoModFile() b.updateGoModFile()
// 3. inject cover vars // 3. inject cover vars
b.Inject() b.Inject()
if b.IsVendorMod && b.IsModEdit { if b.IsVendorMod && b.IsModEdit {
b.reVendor() b.reVendor()
} }
// 4. install in the temp project // 4. install in the temp project
b.doInstallInTemp() b.doInstallInTemp()
} }
func (b *Build) doInstallInTemp() { func (b *Build) doInstallInTemp() {
log.StartWait("installing the injected project") log.StartWait("installing the injected project")
goflags := b.Goflags goflags := b.Goflags
pacakges := b.Packages pacakges := b.Packages
goflags = append(goflags, pacakges...) goflags = append(goflags, pacakges...)
args := []string{"install"} args := []string{"install"}
args = append(args, goflags...) args = append(args, goflags...)
// go 命令行由 go install [build flags] [packages] 组成 // go 命令行由 go install [build flags] [packages] 组成
cmd := exec.Command("go", args...) cmd := exec.Command("go", args...)
cmd.Dir = b.TmpWd cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
log.Infof("go install cmd is: %v, in path [%v]", cmd.Args, cmd.Dir) log.Infof("go install cmd is: %v, in path [%v]", cmd.Args, cmd.Dir)
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
log.Fatalf("fail to execute go install: %v", err) log.Fatalf("fail to execute go install: %v", err)
} }
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
log.Fatalf("fail to execute go install: %v", err) log.Fatalf("fail to execute go install: %v", err)
} }
// done // done
log.StopWait() log.StopWait()
log.Donef("go install done") log.Donef("go install done")
} }

File diff suppressed because it is too large Load Diff

View File

@ -14,14 +14,14 @@
package websocket package websocket
import ( import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"embed" "embed"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
) )
//go:embed websocket.tar //go:embed websocket.tar
@ -32,48 +32,48 @@ var depTarFile embed.FS
// 从 embed 文件系统中解压 websocket.tar 文件,并依次写入临时工程中,作为一个单独的包存在。 // 从 embed 文件系统中解压 websocket.tar 文件,并依次写入临时工程中,作为一个单独的包存在。
// gorrila/websocket 是一个无第三方依赖的库,因此其位置可以随处移动,而不影响自身的编译。 // gorrila/websocket 是一个无第三方依赖的库,因此其位置可以随处移动,而不影响自身的编译。
func AddCustomWebsocketDep(customWebsocketPath string) { func AddCustomWebsocketDep(customWebsocketPath string) {
data, err := depTarFile.ReadFile("websocket.tar") data, err := depTarFile.ReadFile("websocket.tar")
if err != nil { if err != nil {
log.Fatalf("cannot find the websocket.tar in the embed file: %v", err) log.Fatalf("cannot find the websocket.tar in the embed file: %v", err)
} }
buf := bytes.NewBuffer(data) buf := bytes.NewBuffer(data)
tr := tar.NewReader(buf) tr := tar.NewReader(buf)
for { for {
hdr, err := tr.Next() hdr, err := tr.Next()
if err == io.EOF { if err == io.EOF {
break break
} }
if err != nil { if err != nil {
log.Fatalf("cannot untar the websocket.tar: %v", err) log.Fatalf("cannot untar the websocket.tar: %v", err)
} }
fpath := filepath.Join(customWebsocketPath, hdr.Name) fpath := filepath.Join(customWebsocketPath, hdr.Name)
if hdr.FileInfo().IsDir() { if hdr.FileInfo().IsDir() {
// 处理目录 // 处理目录
err := os.MkdirAll(fpath, hdr.FileInfo().Mode()) err := os.MkdirAll(fpath, hdr.FileInfo().Mode())
if err != nil { if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err) log.Fatalf("fail to untar the websocket.tar: %v", err)
} }
} else { } else {
// 处理文件 // 处理文件
fdir := filepath.Dir(fpath) fdir := filepath.Dir(fpath)
err := os.MkdirAll(fdir, hdr.FileInfo().Mode()) err := os.MkdirAll(fdir, hdr.FileInfo().Mode())
if err != nil { if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err) log.Fatalf("fail to untar the websocket.tar: %v", err)
} }
f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, hdr.FileInfo().Mode()) f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, hdr.FileInfo().Mode())
if err != nil { if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err) log.Fatalf("fail to untar the websocket.tar: %v", err)
} }
defer f.Close() defer f.Close()
_, err = io.Copy(f, tr) _, err = io.Copy(f, tr)
if err != nil { if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err) log.Fatalf("fail to untar the websocket.tar: %v", err)
} }
} }
} }
} }

View File

@ -14,33 +14,33 @@
package build package build
import ( import (
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/server" "github.com/ar0c/goc/v2/pkg/server"
"github.com/RickLeee/goc/v2/pkg/server/store" "github.com/ar0c/goc/v2/pkg/server/store"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func NewRun(opts ...gocOption) *Build { func NewRun(opts ...gocOption) *Build {
b := &Build{} b := &Build{}
for _, opt := range opts { for _, opt := range opts {
opt(b) opt(b)
} }
// 1. 解析 goc 命令行和 go 命令行 // 1. 解析 goc 命令行和 go 命令行
b.runCmdArgsParse() b.runCmdArgsParse()
// 2. 解析 go 包位置 // 2. 解析 go 包位置
// b.getPackagesDir() // b.getPackagesDir()
// 3. 读取工程元信息go.mod, pkgs list ... // 3. 读取工程元信息go.mod, pkgs list ...
b.readProjectMetaInfo() b.readProjectMetaInfo()
// 4. 展示元信息 // 4. 展示元信息
b.displayProjectMetaInfo() b.displayProjectMetaInfo()
return b return b
} }
// Run starts go run // Run starts go run
@ -49,58 +49,58 @@ func NewRun(opts ...gocOption) *Build {
// 2. inject cover variables and functions into the project, // 2. inject cover variables and functions into the project,
// 3. run the project in temp. // 3. run the project in temp.
func (b *Build) Run() { func (b *Build) Run() {
// 1. 拷贝至临时目录 // 1. 拷贝至临时目录
b.copyProjectToTmp() b.copyProjectToTmp()
defer b.clean() defer b.clean()
log.Donef("project copied to temporary directory") log.Donef("project copied to temporary directory")
// 2. update go.mod file if needed // 2. update go.mod file if needed
b.updateGoModFile() b.updateGoModFile()
// 3. inject cover vars // 3. inject cover vars
b.Inject() b.Inject()
if b.IsVendorMod && b.IsModEdit { if b.IsVendorMod && b.IsModEdit {
b.reVendor() b.reVendor()
} }
// 4. run in the temp project // 4. run in the temp project
go func() { go func() {
ch := make(chan os.Signal, 1) ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt) signal.Notify(ch, os.Interrupt)
<-ch <-ch
b.clean() b.clean()
}() }()
b.doRunInTemp() b.doRunInTemp()
} }
func (b *Build) doRunInTemp() { func (b *Build) doRunInTemp() {
log.Infof("running the injected project") log.Infof("running the injected project")
s := store.NewFakeStore() s := store.NewFakeStore()
go func() { go func() {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
err := server.RunGocServerUntilExit(b.Host, s) err := server.RunGocServerUntilExit(b.Host, s)
if err != nil { if err != nil {
log.Fatalf("goc server fail to run: %v", err) log.Fatalf("goc server fail to run: %v", err)
} }
}() }()
args := []string{"run"} args := []string{"run"}
args = append(args, b.GoArgs...) args = append(args, b.GoArgs...)
cmd := exec.Command("go", args...) cmd := exec.Command("go", args...)
cmd.Dir = b.TmpWd cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
log.Infof("go run cmd is: %v, in path [%v]", nicePrintArgs(cmd.Args), cmd.Dir) log.Infof("go run cmd is: %v, in path [%v]", nicePrintArgs(cmd.Args), cmd.Dir)
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
log.Errorf("fail to execute go run: %v", err) log.Errorf("fail to execute go run: %v", err)
} }
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
log.Errorf("fail to execute go run: %v", err) log.Errorf("fail to execute go run: %v", err)
} }
// done // done
log.Donef("go run done") log.Donef("go run done")
} }

View File

@ -14,79 +14,79 @@
package build package build
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/tongjingran/copy" "github.com/tongjingran/copy"
"golang.org/x/mod/modfile" "golang.org/x/mod/modfile"
) )
// copyProjectToTmp copies project files to the temporary directory // copyProjectToTmp copies project files to the temporary directory
// //
// It will ignore .git and irregular files, only copy source(text) files // It will ignore .git and irregular files, only copy source(text) files
func (b *Build) copyProjectToTmp() { func (b *Build) copyProjectToTmp() {
curProject := b.CurModProjectDir curProject := b.CurModProjectDir
tmpProject := b.TmpModProjectDir tmpProject := b.TmpModProjectDir
if _, err := os.Stat(tmpProject); !os.IsNotExist(err) { if _, err := os.Stat(tmpProject); !os.IsNotExist(err) {
log.Infof("find previous temporary directory, delete") log.Infof("find previous temporary directory, delete")
err := os.RemoveAll(tmpProject) err := os.RemoveAll(tmpProject)
if err != nil { if err != nil {
log.Fatalf("fail to remove preivous temporary directory: %v", err) log.Fatalf("fail to remove preivous temporary directory: %v", err)
} }
} }
log.StartWait("coping project") log.StartWait("coping project")
err := os.MkdirAll(tmpProject, os.ModePerm) err := os.MkdirAll(tmpProject, os.ModePerm)
if err != nil { if err != nil {
log.Fatalf("fail to create temporary directory: %v", err) log.Fatalf("fail to create temporary directory: %v", err)
} }
// copy // copy
if err := copy.Copy(curProject, tmpProject, copy.Options{Skip: skipCopy}); err != nil { if err := copy.Copy(curProject, tmpProject, copy.Options{Skip: skipCopy}); err != nil {
log.Fatalf("fail to copy the folder from %v to %v, the err: %v", curProject, tmpProject, err) log.Fatalf("fail to copy the folder from %v to %v, the err: %v", curProject, tmpProject, err)
} }
log.StopWait() log.StopWait()
} }
// TmpFolderName generates a directory name according to the path // TmpFolderName generates a directory name according to the path
func TmpFolderName(path string) string { func TmpFolderName(path string) string {
sum := sha256.Sum256([]byte(path)) sum := sha256.Sum256([]byte(path))
h := fmt.Sprintf("%x", sum[:6]) h := fmt.Sprintf("%x", sum[:6])
return "gocbuild" + h return "gocbuild" + h
} }
// skipCopy skip copy .git dir and irregular files // skipCopy skip copy .git dir and irregular files
func skipCopy(src string, info os.FileInfo) (bool, error) { func skipCopy(src string, info os.FileInfo) (bool, error) {
irregularModeType := os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular irregularModeType := os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
if strings.HasSuffix(src, "/.git") { if strings.HasSuffix(src, "/.git") {
log.Debugf("skip .git dir [%s]", src) log.Debugf("skip .git dir [%s]", src)
return true, nil return true, nil
} }
if info.Mode()&irregularModeType != 0 { if info.Mode()&irregularModeType != 0 {
log.Debugf("skip file [%s], the file mode is [%s]", src, info.Mode().String()) log.Debugf("skip file [%s], the file mode is [%s]", src, info.Mode().String())
return true, nil return true, nil
} }
return false, nil return false, nil
} }
// clean clears the temporary project // clean clears the temporary project
func (b *Build) clean() { func (b *Build) clean() {
if !b.Debug { if !b.Debug {
if err := os.RemoveAll(b.TmpModProjectDir); err != nil { if err := os.RemoveAll(b.TmpModProjectDir); err != nil {
log.Fatalf("fail to delete the temporary project: %v", err) log.Fatalf("fail to delete the temporary project: %v", err)
} }
log.Donef("delete the temporary project") log.Donef("delete the temporary project")
} else { } else {
log.Debugf("--debug is enabled, keep the temporary project") log.Debugf("--debug is enabled, keep the temporary project")
} }
} }
// updateGoModFile rewrites the go.mod file in the temporary directory, // updateGoModFile rewrites the go.mod file in the temporary directory,
@ -102,50 +102,50 @@ func (b *Build) clean() {
// after the project is copied to temporary directory, it should be rewritten as // after the project is copied to temporary directory, it should be rewritten as
// 'replace github.com/qiniu/bar => /path/to/aa/bb/home/foo/bar' // 'replace github.com/qiniu/bar => /path/to/aa/bb/home/foo/bar'
func (b *Build) updateGoModFile() (updateFlag bool, newModFile []byte) { func (b *Build) updateGoModFile() (updateFlag bool, newModFile []byte) {
tempModfile := filepath.Join(b.TmpModProjectDir, "go.mod") tempModfile := filepath.Join(b.TmpModProjectDir, "go.mod")
buf, err := ioutil.ReadFile(tempModfile) buf, err := ioutil.ReadFile(tempModfile)
if err != nil { if err != nil {
log.Fatalf("cannot find go.mod file in temporary directory: %v", err) log.Fatalf("cannot find go.mod file in temporary directory: %v", err)
} }
oriGoModFile, err := modfile.Parse(tempModfile, buf, nil) oriGoModFile, err := modfile.Parse(tempModfile, buf, nil)
if err != nil { if err != nil {
log.Fatalf("cannot parse go.mod: %v", err) log.Fatalf("cannot parse go.mod: %v", err)
} }
updateFlag = false updateFlag = false
for index := range oriGoModFile.Replace { for index := range oriGoModFile.Replace {
replace := oriGoModFile.Replace[index] replace := oriGoModFile.Replace[index]
oldPath := replace.Old.Path oldPath := replace.Old.Path
oldVersion := replace.Old.Version oldVersion := replace.Old.Version
newPath := replace.New.Path newPath := replace.New.Path
newVersion := replace.New.Version newVersion := replace.New.Version
// replace to a local filesystem does not have a version // replace to a local filesystem does not have a version
// absolute path no need to rewrite // absolute path no need to rewrite
if newVersion == "" && !filepath.IsAbs(newPath) { if newVersion == "" && !filepath.IsAbs(newPath) {
var absPath string var absPath string
fullPath := filepath.Join(b.CurModProjectDir, newPath) fullPath := filepath.Join(b.CurModProjectDir, newPath)
absPath, _ = filepath.Abs(fullPath) absPath, _ = filepath.Abs(fullPath)
// DropReplace & AddReplace will not return error // DropReplace & AddReplace will not return error
// so no need to check the error // so no need to check the error
_ = oriGoModFile.DropReplace(oldPath, oldVersion) _ = oriGoModFile.DropReplace(oldPath, oldVersion)
_ = oriGoModFile.AddReplace(oldPath, oldVersion, absPath, newVersion) _ = oriGoModFile.AddReplace(oldPath, oldVersion, absPath, newVersion)
updateFlag = true updateFlag = true
} }
} }
oriGoModFile.Cleanup() oriGoModFile.Cleanup()
// Format will not return error, so ignore the returned error // Format will not return error, so ignore the returned error
// func (f *File) Format() ([]byte, error) { // func (f *File) Format() ([]byte, error) {
// return Format(f.Syntax), nil // return Format(f.Syntax), nil
// } // }
newModFile, _ = oriGoModFile.Format() newModFile, _ = oriGoModFile.Format()
if updateFlag { if updateFlag {
log.Infof("go.mod needs rewrite") log.Infof("go.mod needs rewrite")
err := os.WriteFile(tempModfile, newModFile, os.ModePerm) err := os.WriteFile(tempModfile, newModFile, os.ModePerm)
if err != nil { if err != nil {
log.Fatalf("fail to update go.mod: %v", err) log.Fatalf("fail to update go.mod: %v", err)
} }
b.IsModEdit = true b.IsModEdit = true
} }
return return
} }

View File

@ -14,83 +14,83 @@
package client package client
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"github.com/RickLeee/goc/v2/pkg/client/rest" "github.com/ar0c/goc/v2/pkg/client/rest"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
) )
const ( const (
DISCONNECT = 1 << iota DISCONNECT = 1 << iota
RPCCONNECT = 1 << iota RPCCONNECT = 1 << iota
WATCHCONNECT = 1 << iota WATCHCONNECT = 1 << iota
) )
func ListAgents(host string, ids []string, wide, isJson bool) { func ListAgents(host string, ids []string, wide, isJson bool) {
gocClient := rest.NewV2Client(host) gocClient := rest.NewV2Client(host)
agents, err := gocClient.Agent().Get(ids) agents, err := gocClient.Agent().Get(ids)
if err != nil { if err != nil {
log.Fatalf("cannot get agent list from goc server: %v", err) log.Fatalf("cannot get agent list from goc server: %v", err)
} }
table := tablewriter.NewWriter(os.Stdout) table := tablewriter.NewWriter(os.Stdout)
if isJson { if isJson {
goto asJson goto asJson
} }
table.SetCenterSeparator("") table.SetCenterSeparator("")
table.SetColumnSeparator("") table.SetColumnSeparator("")
table.SetRowSeparator("") table.SetRowSeparator("")
table.SetHeaderLine(false) table.SetHeaderLine(false)
table.SetBorder(false) table.SetBorder(false)
table.SetTablePadding(" ") // pad with 3 blank spaces table.SetTablePadding(" ") // pad with 3 blank spaces
table.SetNoWhiteSpace(true) table.SetNoWhiteSpace(true)
table.SetReflowDuringAutoWrap(false) table.SetReflowDuringAutoWrap(false)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoWrapText(false) table.SetAutoWrapText(false)
if wide { if wide {
table.SetHeader([]string{"ID", "STATUS", "REMOTEIP", "HOSTNAME", "PID", "CMD", "EXTRA"}) table.SetHeader([]string{"ID", "STATUS", "REMOTEIP", "HOSTNAME", "PID", "CMD", "EXTRA"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
} else { } else {
table.SetHeader([]string{"ID", "STATUS", "REMOTEIP", "CMD"}) table.SetHeader([]string{"ID", "STATUS", "REMOTEIP", "CMD"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
} }
asJson: asJson:
for _, agent := range agents { for _, agent := range agents {
var status string var status string
if agent.Status == DISCONNECT { if agent.Status == DISCONNECT {
status = "DISCONNECT" status = "DISCONNECT"
} else if agent.Status&(RPCCONNECT|WATCHCONNECT) > 0 { } else if agent.Status&(RPCCONNECT|WATCHCONNECT) > 0 {
status = "CONNECT" status = "CONNECT"
} }
agent.StatusStr = status agent.StatusStr = status
if !isJson { if !isJson {
if wide { if wide {
table.Append([]string{agent.Id, status, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine, agent.Extra}) table.Append([]string{agent.Id, status, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine, agent.Extra})
} else { } else {
preLen := len(agent.Id) + len(agent.RemoteIP) + 9 preLen := len(agent.Id) + len(agent.RemoteIP) + 9
table.Append([]string{agent.Id, status, agent.RemoteIP, getSimpleCmdLine(preLen, agent.CmdLine)}) table.Append([]string{agent.Id, status, agent.RemoteIP, getSimpleCmdLine(preLen, agent.CmdLine)})
} }
} }
} }
if !isJson { if !isJson {
table.Render() table.Render()
} else { } else {
b, _ := json.Marshal(agents) b, _ := json.Marshal(agents)
fmt.Fprint(os.Stdout, string(b)) fmt.Fprint(os.Stdout, string(b))
} }
} }
func DeleteAgents(host string, ids []string) { func DeleteAgents(host string, ids []string) {
gocClient := rest.NewV2Client(host) gocClient := rest.NewV2Client(host)
err := gocClient.Agent().Delete(ids) err := gocClient.Agent().Delete(ids)
if err != nil { if err != nil {
log.Fatalf("cannot delete agents from goc server: %v", err) log.Fatalf("cannot delete agents from goc server: %v", err)
} }
} }

View File

@ -14,198 +14,198 @@
package client package client
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"golang.org/x/term" "golang.org/x/term"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
) )
// Action provides methods to contact with the covered agent under test // Action provides methods to contact with the covered agent under test
type Action interface { type Action interface {
ListAgents(bool) ListAgents(bool)
Profile(string) Profile(string)
} }
const ( const (
// CoverAgentsListAPI list all the registered agents // CoverAgentsListAPI list all the registered agents
CoverAgentsListAPI = "/v2/rpcagents" CoverAgentsListAPI = "/v2/rpcagents"
//CoverProfileAPI is provided by the covered service to get profiles //CoverProfileAPI is provided by the covered service to get profiles
CoverProfileAPI = "/v2/cover/profile" CoverProfileAPI = "/v2/cover/profile"
) )
type client struct { type client struct {
Host string Host string
client *http.Client client *http.Client
} }
// gocListAgents response of the list request // gocListAgents response of the list request
type gocListAgents struct { type gocListAgents struct {
Items []gocCoveredAgent `json:"items"` Items []gocCoveredAgent `json:"items"`
} }
// gocCoveredAgent represents a covered client // gocCoveredAgent represents a covered client
type gocCoveredAgent struct { type gocCoveredAgent struct {
Id string `json:"id"` Id string `json:"id"`
RemoteIP string `json:"remoteip"` RemoteIP string `json:"remoteip"`
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
CmdLine string `json:"cmdline"` CmdLine string `json:"cmdline"`
Pid string `json:"pid"` Pid string `json:"pid"`
} }
type gocProfile struct { type gocProfile struct {
Profile string `json:"profile"` Profile string `json:"profile"`
} }
// NewWorker creates a worker to contact with host // NewWorker creates a worker to contact with host
func NewWorker(host string) Action { func NewWorker(host string) Action {
_, err := url.ParseRequestURI(host) _, err := url.ParseRequestURI(host)
if err != nil { if err != nil {
log.Fatalf("parse url %s failed, err: %v", host, err) log.Fatalf("parse url %s failed, err: %v", host, err)
} }
return &client{ return &client{
Host: host, Host: host,
client: http.DefaultClient, client: http.DefaultClient,
} }
} }
// ListAgents Deprecated // ListAgents Deprecated
func (c *client) ListAgents(wide bool) { func (c *client) ListAgents(wide bool) {
u := fmt.Sprintf("%s%s", c.Host, CoverAgentsListAPI) u := fmt.Sprintf("%s%s", c.Host, CoverAgentsListAPI)
_, body, err := c.do("GET", u, "", nil) _, body, err := c.do("GET", u, "", nil)
if err != nil && isNetworkError(err) { if err != nil && isNetworkError(err) {
_, body, err = c.do("GET", u, "", nil) _, body, err = c.do("GET", u, "", nil)
} }
if err != nil { if err != nil {
log.Fatalf("goc list failed: %v", err) log.Fatalf("goc list failed: %v", err)
} }
agents := gocListAgents{} agents := gocListAgents{}
err = json.Unmarshal(body, &agents) err = json.Unmarshal(body, &agents)
if err != nil { if err != nil {
log.Fatalf("goc list failed: json unmarshal failed: %v", err) log.Fatalf("goc list failed: json unmarshal failed: %v", err)
} }
table := tablewriter.NewWriter(os.Stdout) table := tablewriter.NewWriter(os.Stdout)
table.SetCenterSeparator("") table.SetCenterSeparator("")
table.SetColumnSeparator("") table.SetColumnSeparator("")
table.SetRowSeparator("") table.SetRowSeparator("")
table.SetHeaderLine(false) table.SetHeaderLine(false)
table.SetBorder(false) table.SetBorder(false)
table.SetTablePadding(" ") // pad with 3 blank spaces table.SetTablePadding(" ") // pad with 3 blank spaces
table.SetNoWhiteSpace(true) table.SetNoWhiteSpace(true)
table.SetReflowDuringAutoWrap(false) table.SetReflowDuringAutoWrap(false)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoWrapText(false) table.SetAutoWrapText(false)
if wide { if wide {
table.SetHeader([]string{"ID", "REMOTEIP", "HOSTNAME", "PID", "CMD"}) table.SetHeader([]string{"ID", "REMOTEIP", "HOSTNAME", "PID", "CMD"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
} else { } else {
table.SetHeader([]string{"ID", "REMOTEIP", "CMD"}) table.SetHeader([]string{"ID", "REMOTEIP", "CMD"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
} }
for _, agent := range agents.Items { for _, agent := range agents.Items {
if wide { if wide {
table.Append([]string{agent.Id, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine}) table.Append([]string{agent.Id, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine})
} else { } else {
preLen := len(agent.Id) + len(agent.RemoteIP) + 9 preLen := len(agent.Id) + len(agent.RemoteIP) + 9
table.Append([]string{agent.Id, agent.RemoteIP, getSimpleCmdLine(preLen, agent.CmdLine)}) table.Append([]string{agent.Id, agent.RemoteIP, getSimpleCmdLine(preLen, agent.CmdLine)})
} }
} }
table.Render() table.Render()
return return
} }
func (c *client) Profile(output string) { func (c *client) Profile(output string) {
u := fmt.Sprintf("%s%s", c.Host, CoverProfileAPI) u := fmt.Sprintf("%s%s", c.Host, CoverProfileAPI)
res, profile, err := c.do("GET", u, "application/json", nil) res, profile, err := c.do("GET", u, "application/json", nil)
if err != nil && isNetworkError(err) { if err != nil && isNetworkError(err) {
res, profile, err = c.do("GET", u, "application/json", nil) res, profile, err = c.do("GET", u, "application/json", nil)
} }
if err == nil && res.StatusCode != 200 { if err == nil && res.StatusCode != 200 {
log.Fatalf(string(profile)) log.Fatalf(string(profile))
} }
var profileText gocProfile var profileText gocProfile
err = json.Unmarshal(profile, &profileText) err = json.Unmarshal(profile, &profileText)
if err != nil { if err != nil {
log.Fatalf("profile unmarshal failed: %v", err) log.Fatalf("profile unmarshal failed: %v", err)
} }
if output == "" { if output == "" {
fmt.Fprint(os.Stdout, profileText.Profile) fmt.Fprint(os.Stdout, profileText.Profile)
} else { } else {
var dir, filename string = filepath.Split(output) var dir, filename string = filepath.Split(output)
if dir != "" { if dir != "" {
err = os.MkdirAll(dir, os.ModePerm) err = os.MkdirAll(dir, os.ModePerm)
if err != nil { if err != nil {
log.Fatalf("failed to create directory %s, err:%v", dir, err) log.Fatalf("failed to create directory %s, err:%v", dir, err)
} }
} }
if filename == "" { if filename == "" {
output += "coverage.cov" output += "coverage.cov"
} }
f, err := os.Create(output) f, err := os.Create(output)
if err != nil { if err != nil {
log.Fatalf("failed to create file %s, err:%v", output, err) log.Fatalf("failed to create file %s, err:%v", output, err)
} }
defer f.Close() defer f.Close()
_, err = io.Copy(f, bytes.NewReader([]byte(profileText.Profile))) _, err = io.Copy(f, bytes.NewReader([]byte(profileText.Profile)))
if err != nil { if err != nil {
log.Fatalf("failed to write file: %v, err: %v", output, err) log.Fatalf("failed to write file: %v, err: %v", output, err)
} }
} }
} }
// getSimpleCmdLine // getSimpleCmdLine
func getSimpleCmdLine(preLen int, cmdLine string) string { func getSimpleCmdLine(preLen int, cmdLine string) string {
pathLen := len(cmdLine) pathLen := len(cmdLine)
width, _, err := term.GetSize(int(os.Stdin.Fd())) width, _, err := term.GetSize(int(os.Stdin.Fd()))
if err != nil || width <= preLen+16 { if err != nil || width <= preLen+16 {
width = 16 + preLen // show at least 16 words of the command width = 16 + preLen // show at least 16 words of the command
} }
if pathLen > width-preLen { if pathLen > width-preLen {
return cmdLine[:width-preLen] return cmdLine[:width-preLen]
} }
return cmdLine return cmdLine
} }
func (c *client) do(method, url, contentType string, body io.Reader) (*http.Response, []byte, error) { func (c *client) do(method, url, contentType string, body io.Reader) (*http.Response, []byte, error) {
req, err := http.NewRequest(method, url, body) req, err := http.NewRequest(method, url, body)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if contentType != "" { if contentType != "" {
req.Header.Set("Content-Type", contentType) req.Header.Set("Content-Type", contentType)
} }
res, err := c.client.Do(req) res, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer res.Body.Close() defer res.Body.Close()
responseBody, err := ioutil.ReadAll(res.Body) responseBody, err := ioutil.ReadAll(res.Body)
if err != nil { if err != nil {
return res, nil, err return res, nil, err
} }
return res, responseBody, nil return res, responseBody, nil
} }
func isNetworkError(err error) bool { func isNetworkError(err error) bool {
if err == io.EOF { if err == io.EOF {
return true return true
} }
_, ok := err.(net.Error) _, ok := err.(net.Error)
return ok return ok
} }

View File

@ -14,61 +14,61 @@
package client package client
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"github.com/RickLeee/goc/v2/pkg/client/rest" "github.com/ar0c/goc/v2/pkg/client/rest"
"github.com/RickLeee/goc/v2/pkg/client/rest/profile" "github.com/ar0c/goc/v2/pkg/client/rest/profile"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
) )
func GetProfile(host string, ids []string, skips []string, extra string, output string, need []string) { func GetProfile(host string, ids []string, skips []string, extra string, output string, need []string) {
gocClient := rest.NewV2Client(host) gocClient := rest.NewV2Client(host)
profiles, err := gocClient.Profile().Get(ids, profiles, err := gocClient.Profile().Get(ids,
profile.WithPackagePattern(skips), profile.WithPackagePattern(skips),
profile.WithExtraPattern(extra), profile.WithExtraPattern(extra),
profile.WithNeed(need)) profile.WithNeed(need))
if err != nil { if err != nil {
log.Fatalf("fail to get profile from the goc server: %v, response: %v", err, profiles) log.Fatalf("fail to get profile from the goc server: %v, response: %v", err, profiles)
} }
if output == "" { if output == "" {
fmt.Fprint(os.Stdout, profiles) fmt.Fprint(os.Stdout, profiles)
} else { } else {
var dir, filename string = filepath.Split(output) var dir, filename string = filepath.Split(output)
if dir != "" { if dir != "" {
err = os.MkdirAll(dir, os.ModePerm) err = os.MkdirAll(dir, os.ModePerm)
if err != nil { if err != nil {
log.Fatalf("failed to create directory %s, err:%v", dir, err) log.Fatalf("failed to create directory %s, err:%v", dir, err)
} }
} }
if filename == "" { if filename == "" {
output += "coverage.cov" output += "coverage.cov"
} }
f, err := os.Create(output) f, err := os.Create(output)
if err != nil { if err != nil {
log.Fatalf("failed to create file %s, err:%v", output, err) log.Fatalf("failed to create file %s, err:%v", output, err)
} }
defer f.Close() defer f.Close()
_, err = io.Copy(f, bytes.NewReader([]byte(profiles))) _, err = io.Copy(f, bytes.NewReader([]byte(profiles)))
if err != nil { if err != nil {
log.Fatalf("failed to write file: %v, err: %v", output, err) log.Fatalf("failed to write file: %v, err: %v", output, err)
} }
} }
} }
func ClearProfile(host string, ids []string, extra string) { func ClearProfile(host string, ids []string, extra string) {
gocClient := rest.NewV2Client(host) gocClient := rest.NewV2Client(host)
err := gocClient.Profile().Delete(ids, err := gocClient.Profile().Delete(ids,
profile.WithExtraPattern(extra)) profile.WithExtraPattern(extra))
if err != nil { if err != nil {
log.Fatalf("fail to clear the profile: %v", err) log.Fatalf("fail to clear the profile: %v", err)
} }
} }

View File

@ -14,26 +14,26 @@
package rest package rest
import ( import (
"github.com/RickLeee/goc/v2/pkg/client/rest/agent" "github.com/ar0c/goc/v2/pkg/client/rest/agent"
"github.com/RickLeee/goc/v2/pkg/client/rest/profile" "github.com/ar0c/goc/v2/pkg/client/rest/profile"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
) )
// V2Client provides methods contact with the covered agent under test // V2Client provides methods contact with the covered agent under test
type V2Client struct { type V2Client struct {
rest *resty.Client rest *resty.Client
} }
func NewV2Client(host string) *V2Client { func NewV2Client(host string) *V2Client {
return &V2Client{ return &V2Client{
rest: resty.New().SetHostURL("http://" + host), rest: resty.New().SetHostURL("http://" + host),
} }
} }
func (c *V2Client) Agent() agent.AgentInterface { func (c *V2Client) Agent() agent.AgentInterface {
return agent.NewAgentsClient(c.rest) return agent.NewAgentsClient(c.rest)
} }
func (c *V2Client) Profile() profile.ProfileInterface { func (c *V2Client) Profile() profile.ProfileInterface {
return profile.NewProfileClient(c.rest) return profile.NewProfileClient(c.rest)
} }

View File

@ -14,390 +14,390 @@
package server package server
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"golang.org/x/tools/cover" "golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov" "k8s.io/test-infra/gopherage/pkg/cov"
) )
// listAgents return all service informations // listAgents return all service informations
func (gs *gocServer) listAgents(c *gin.Context) { func (gs *gocServer) listAgents(c *gin.Context) {
idQuery := c.Query("id") idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery) ifInIdMap := idMaps(idQuery)
agents := make([]*gocCoveredAgent, 0) agents := make([]*gocCoveredAgent, 0)
gs.agents.Range(func(key, value interface{}) bool { gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids // check if id is in the query ids
if !ifInIdMap(key.(string)) { if !ifInIdMap(key.(string)) {
return true return true
} }
agent, ok := value.(*gocCoveredAgent) agent, ok := value.(*gocCoveredAgent)
if !ok { if !ok {
return false return false
} }
agents = append(agents, agent) agents = append(agents, agent)
return true return true
}) })
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"items": agents, "items": agents,
}) })
} }
func (gs *gocServer) removeAgents(c *gin.Context) { func (gs *gocServer) removeAgents(c *gin.Context) {
idQuery := c.Query("id") idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery) ifInIdMap := idMaps(idQuery)
errs := "" errs := ""
gs.agents.Range(func(key, value interface{}) bool { gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids // check if id is in the query ids
id := key.(string) id := key.(string)
if !ifInIdMap(id) { if !ifInIdMap(id) {
return true return true
} }
agent, ok := value.(*gocCoveredAgent) agent, ok := value.(*gocCoveredAgent)
if !ok { if !ok {
return false return false
} }
err := gs.removeAgentFromStore(id) err := gs.removeAgentFromStore(id)
if err != nil { if err != nil {
log.Errorf("fail to remove agent: %v", id) log.Errorf("fail to remove agent: %v", id)
err := fmt.Errorf("fail to remove agent: %v, err: %v", id, err) err := fmt.Errorf("fail to remove agent: %v, err: %v", id, err)
errs = errs + err.Error() errs = errs + err.Error()
return true return true
} }
agent.closeConnection() agent.closeConnection()
gs.agents.Delete(key) gs.agents.Delete(key)
return true return true
}) })
if errs != "" { if errs != "" {
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
"msg": errs, "msg": errs,
}) })
} else { } else {
c.JSON(http.StatusOK, nil) c.JSON(http.StatusOK, nil)
} }
} }
// getProfiles get and merge all agents' informations // getProfiles get and merge all agents' informations
// //
// it is synchronous // it is synchronous
func (gs *gocServer) getProfiles(c *gin.Context) { func (gs *gocServer) getProfiles(c *gin.Context) {
idQuery := c.Query("id") idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery) ifInIdMap := idMaps(idQuery)
skippatternRaw := c.Query("skippattern") skippatternRaw := c.Query("skippattern")
var skippattern []string var skippattern []string
if skippatternRaw != "" { if skippatternRaw != "" {
skippattern = strings.Split(skippatternRaw, ",") skippattern = strings.Split(skippatternRaw, ",")
} }
neerpatternRaw := c.Query("needpattern") neerpatternRaw := c.Query("needpattern")
var neerpattern []string var neerpattern []string
if neerpatternRaw != "" { if neerpatternRaw != "" {
neerpattern = strings.Split(neerpatternRaw, ",") neerpattern = strings.Split(neerpatternRaw, ",")
} }
extra := c.Query("extra") extra := c.Query("extra")
isExtra := filterExtra(extra) isExtra := filterExtra(extra)
var mu sync.Mutex var mu sync.Mutex
var wg sync.WaitGroup var wg sync.WaitGroup
mergedProfiles := make([][]*cover.Profile, 0) mergedProfiles := make([][]*cover.Profile, 0)
gs.agents.Range(func(key, value interface{}) bool { gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids // check if id is in the query ids
if !ifInIdMap(key.(string)) { if !ifInIdMap(key.(string)) {
// not in // not in
return true return true
} }
agent, ok := value.(*gocCoveredAgent) agent, ok := value.(*gocCoveredAgent)
if !ok { if !ok {
return false return false
} }
// check if extra matches // check if extra matches
if !isExtra(agent.Extra) { if !isExtra(agent.Extra) {
// not match // not match
return true return true
} }
wg.Add(1) wg.Add(1)
// 并发 rpc且每个 rpc 设超时时间 10 second // 并发 rpc且每个 rpc 设超时时间 10 second
go func() { go func() {
defer wg.Done() defer wg.Done()
timeout := time.Duration(10 * time.Second) timeout := time.Duration(10 * time.Second)
done := make(chan error, 1) done := make(chan error, 1)
var req ProfileReq = "getprofile" var req ProfileReq = "getprofile"
var res ProfileRes var res ProfileRes
go func() { go func() {
// lock-free // lock-free
rpc := agent.rpc rpc := agent.rpc
if rpc == nil || agent.Status == DISCONNECT { if rpc == nil || agent.Status == DISCONNECT {
done <- nil done <- nil
return return
} }
err := agent.rpc.Call("GocAgent.GetProfile", req, &res) err := agent.rpc.Call("GocAgent.GetProfile", req, &res)
if err != nil { if err != nil {
log.Errorf("fail to get profile from: %v, reasson: %v. let's close the connection", agent.Id, err) log.Errorf("fail to get profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
} }
done <- err done <- err
}() }()
select { select {
// rpc 超时 // rpc 超时
case <-time.After(timeout): case <-time.After(timeout):
log.Warnf("rpc call timeout: %v", agent.Hostname) log.Warnf("rpc call timeout: %v", agent.Hostname)
// 关闭链接 // 关闭链接
agent.closeRpcConnOnce() agent.closeRpcConnOnce()
case err := <-done: case err := <-done:
// 调用 rpc 发生错误 // 调用 rpc 发生错误
if err != nil { if err != nil {
// 关闭链接 // 关闭链接
agent.closeRpcConnOnce() agent.closeRpcConnOnce()
} }
} }
// append profile // append profile
profile, err := convertProfile([]byte(res)) profile, err := convertProfile([]byte(res))
if err != nil { if err != nil {
log.Errorf("fail to convert the received profile from: %v, reasson: %v. let's close the connection", agent.Id, err) log.Errorf("fail to convert the received profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
// 关闭链接 // 关闭链接
agent.closeRpcConnOnce() agent.closeRpcConnOnce()
return return
} }
// check if skippattern matches // check if skippattern matches
newProfile := filterProfileByPattern(skippattern, neerpattern, profile) newProfile := filterProfileByPattern(skippattern, neerpattern, profile)
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
mergedProfiles = append(mergedProfiles, newProfile) mergedProfiles = append(mergedProfiles, newProfile)
}() }()
return true return true
}) })
// 一直等待并发的 rpc 都回应 // 一直等待并发的 rpc 都回应
wg.Wait() wg.Wait()
merged, err := cov.MergeMultipleProfiles(mergedProfiles) merged, err := cov.MergeMultipleProfiles(mergedProfiles)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(), "msg": err.Error(),
}) })
return return
} }
var buff bytes.Buffer var buff bytes.Buffer
err = cov.DumpProfile(merged, &buff) err = cov.DumpProfile(merged, &buff)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(), "msg": err.Error(),
}) })
return return
} }
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"profile": buff.String(), "profile": buff.String(),
}) })
} }
// resetProfiles reset all profiles in agent // resetProfiles reset all profiles in agent
// //
// it is async, the function will return immediately // it is async, the function will return immediately
func (gs *gocServer) resetProfiles(c *gin.Context) { func (gs *gocServer) resetProfiles(c *gin.Context) {
idQuery := c.Query("id") idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery) ifInIdMap := idMaps(idQuery)
extra := c.Query("extra") extra := c.Query("extra")
isExtra := filterExtra(extra) isExtra := filterExtra(extra)
gs.agents.Range(func(key, value interface{}) bool { gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids // check if id is in the query ids
if !ifInIdMap(key.(string)) { if !ifInIdMap(key.(string)) {
// not in // not in
return true return true
} }
agent, ok := value.(*gocCoveredAgent) agent, ok := value.(*gocCoveredAgent)
if !ok { if !ok {
return false return false
} }
// check if extra matches // check if extra matches
if !isExtra(agent.Extra) { if !isExtra(agent.Extra) {
// not match // not match
return true return true
} }
var req ProfileReq = "resetprofile" var req ProfileReq = "resetprofile"
var res ProfileRes var res ProfileRes
go func() { go func() {
// lock-free // lock-free
rpc := agent.rpc rpc := agent.rpc
if rpc == nil || agent.Status == DISCONNECT { if rpc == nil || agent.Status == DISCONNECT {
return return
} }
err := rpc.Call("GocAgent.ResetProfile", req, &res) err := rpc.Call("GocAgent.ResetProfile", req, &res)
if err != nil { if err != nil {
log.Errorf("fail to reset profile from: %v, reasson: %v. let's close the connection", agent.Id, err) log.Errorf("fail to reset profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
// 关闭链接 // 关闭链接
agent.closeRpcConnOnce() agent.closeRpcConnOnce()
} }
}() }()
return true return true
}) })
} }
// watchProfileUpdate watch the profile change // watchProfileUpdate watch the profile change
// //
// any profile change will be updated on this websocket connection. // any profile change will be updated on this websocket connection.
func (gs *gocServer) watchProfileUpdate(c *gin.Context) { func (gs *gocServer) watchProfileUpdate(c *gin.Context) {
// upgrade to websocket // upgrade to websocket
ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil) ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
log.Errorf("fail to establish websocket connection with watch client: %v", err) log.Errorf("fail to establish websocket connection with watch client: %v", err)
c.JSON(http.StatusInternalServerError, nil) c.JSON(http.StatusInternalServerError, nil)
} }
log.Infof("watch client connected") log.Infof("watch client connected")
id := time.Now().String() id := time.Now().String()
gwc := &gocWatchClient{ gwc := &gocWatchClient{
ws: ws, ws: ws,
exitCh: make(chan int), exitCh: make(chan int),
} }
gs.watchClients.Store(id, gwc) gs.watchClients.Store(id, gwc)
// send close msg and close ws connection // send close msg and close ws connection
defer func() { defer func() {
gs.watchClients.Delete(id) gs.watchClients.Delete(id)
ws.Close() ws.Close()
gwc.once.Do(func() { close(gwc.exitCh) }) gwc.once.Do(func() { close(gwc.exitCh) })
log.Infof("watch client disconnected") log.Infof("watch client disconnected")
}() }()
// set pong handler // set pong handler
ws.SetReadDeadline(time.Now().Add(PongWait)) ws.SetReadDeadline(time.Now().Add(PongWait))
ws.SetPongHandler(func(string) error { ws.SetPongHandler(func(string) error {
ws.SetReadDeadline(time.Now().Add(PongWait)) ws.SetReadDeadline(time.Now().Add(PongWait))
return nil return nil
}) })
// set ping goroutine to ping every PingWait time // set ping goroutine to ping every PingWait time
go func() { go func() {
ticker := time.NewTicker(PingWait) ticker := time.NewTicker(PingWait)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
if err := gs.wsping(ws, PongWait); err != nil { if err := gs.wsping(ws, PongWait); err != nil {
break break
} }
} }
gwc.once.Do(func() { close(gwc.exitCh) }) gwc.once.Do(func() { close(gwc.exitCh) })
}() }()
<-gwc.exitCh <-gwc.exitCh
} }
func filterProfileByPattern(skippattern []string, needpattern []string, profiles []*cover.Profile) []*cover.Profile { func filterProfileByPattern(skippattern []string, needpattern []string, profiles []*cover.Profile) []*cover.Profile {
var out = make([]*cover.Profile, 0) var out = make([]*cover.Profile, 0)
var skipOut = make([]*cover.Profile, 0) var skipOut = make([]*cover.Profile, 0)
if len(skippattern) == 0 && len(needpattern) == 0 { if len(skippattern) == 0 && len(needpattern) == 0 {
return profiles return profiles
} }
if len(skippattern) != 0 { if len(skippattern) != 0 {
for _, profile := range profiles { for _, profile := range profiles {
skip := false skip := false
for _, pattern := range skippattern { for _, pattern := range skippattern {
if strings.Contains(profile.FileName, pattern) { if strings.Contains(profile.FileName, pattern) {
skip = true skip = true
break break
} }
} }
if !skip { if !skip {
skipOut = append(skipOut, profile) skipOut = append(skipOut, profile)
} }
} }
} else { } else {
skipOut = profiles skipOut = profiles
} }
log.Infof("skipOut len: %v", len(skipOut)) log.Infof("skipOut len: %v", len(skipOut))
if len(needpattern) == 0 { if len(needpattern) == 0 {
return skipOut return skipOut
} }
for _, profile := range skipOut { for _, profile := range skipOut {
need := false need := false
for _, pattern := range needpattern { for _, pattern := range needpattern {
if strings.Contains(profile.FileName, pattern) { if strings.Contains(profile.FileName, pattern) {
need = true need = true
break break
} }
} }
if need { if need {
out = append(out, profile) out = append(out, profile)
} }
} }
log.Infof("need out len: %v", len(out)) log.Infof("need out len: %v", len(out))
return out return out
} }
func idMaps(idQuery string) func(key string) bool { func idMaps(idQuery string) func(key string) bool {
idMap := make(map[string]bool) idMap := make(map[string]bool)
if len(strings.TrimSpace(idQuery)) == 0 { if len(strings.TrimSpace(idQuery)) == 0 {
} else { } else {
ids := strings.Split(idQuery, ",") ids := strings.Split(idQuery, ",")
for _, id := range ids { for _, id := range ids {
idMap[id] = true idMap[id] = true
} }
} }
inIdMaps := func(key string) bool { inIdMaps := func(key string) bool {
// if no id in query, then all id agent will be return // if no id in query, then all id agent will be return
if len(idMap) == 0 { if len(idMap) == 0 {
return true return true
} }
// other // other
_, ok := idMap[key] _, ok := idMap[key]
if !ok { if !ok {
return false return false
} else { } else {
return true return true
} }
} }
return inIdMaps return inIdMaps
} }
func filterExtra(extraPattern string) func(string) bool { func filterExtra(extraPattern string) func(string) bool {
re := regexp.MustCompile(extraPattern) re := regexp.MustCompile(extraPattern)
return func(extra string) bool { return func(extra string) bool {
return re.Match([]byte(extra)) return re.Match([]byte(extra))
} }
} }

View File

@ -14,16 +14,16 @@
package server package server
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"net/http" "net/http"
"net/rpc" "net/rpc"
"net/rpc/jsonrpc" "net/rpc/jsonrpc"
"sync" "sync"
"time" "time"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// serveRpcStream holds connection between goc server and agent. // serveRpcStream holds connection between goc server and agent.
@ -32,114 +32,114 @@ import (
// //
// 2. 每个链接的 goc agent 作为 rpc 服务端 // 2. 每个链接的 goc agent 作为 rpc 服务端
func (gs *gocServer) serveRpcStream(c *gin.Context) { func (gs *gocServer) serveRpcStream(c *gin.Context) {
// 检查插桩服务上报的信息 // 检查插桩服务上报的信息
rpcRemoteIP, _ := c.RemoteIP() rpcRemoteIP, _ := c.RemoteIP()
id := c.Query("id") id := c.Query("id")
token := c.Query("token") token := c.Query("token")
rawagent, ok := gs.agents.Load(id) rawagent, ok := gs.agents.Load(id)
if !ok { if !ok {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"msg": "agent not registered", "msg": "agent not registered",
"code": 1, "code": 1,
}) })
return return
} }
agent := rawagent.(*gocCoveredAgent) agent := rawagent.(*gocCoveredAgent)
if agent.Token != token { if agent.Token != token {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"msg": "register token not match", "msg": "register token not match",
"code": 1, "code": 1,
}) })
return return
} }
// 更新 agent 信息 // 更新 agent 信息
agent.RpcRemoteIP = rpcRemoteIP.String() agent.RpcRemoteIP = rpcRemoteIP.String()
agent.exitCh = make(chan int) agent.exitCh = make(chan int)
agent.Status &= ^DISCONNECT // 取消 DISCONNECT 的状态 agent.Status &= ^DISCONNECT // 取消 DISCONNECT 的状态
agent.Status |= RPCCONNECT // 设置为 RPC CONNECT 状态 agent.Status |= RPCCONNECT // 设置为 RPC CONNECT 状态
// 注册销毁函数 // 注册销毁函数
var once sync.Once var once sync.Once
agent.closeRpcConnOnce = func() { agent.closeRpcConnOnce = func() {
once.Do(func() { once.Do(func() {
// 为什么只是关闭 channel其它资源如何释放 // 为什么只是关闭 channel其它资源如何释放
// close channel 后,本 goroutine 会进入到 defer // close channel 后,本 goroutine 会进入到 defer
close(agent.exitCh) close(agent.exitCh)
}) })
} }
// upgrade to websocket // upgrade to websocket
ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil) ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
log.Errorf("fail to establish websocket connection with rpc agent: %v", err) log.Errorf("fail to establish websocket connection with rpc agent: %v", err)
c.JSON(http.StatusInternalServerError, nil) c.JSON(http.StatusInternalServerError, nil)
} }
// send close msg and close ws connection // send close msg and close ws connection
defer func() { defer func() {
deadline := 1 * time.Second deadline := 1 * time.Second
// 发送 close msg // 发送 close msg
gs.wsclose(ws, deadline) gs.wsclose(ws, deadline)
time.Sleep(deadline) time.Sleep(deadline)
// 取消 RPC CONNECT 状态 // 取消 RPC CONNECT 状态
agent.Status &= ^RPCCONNECT agent.Status &= ^RPCCONNECT
if agent.Status == 0 { if agent.Status == 0 {
agent.Status = DISCONNECT agent.Status = DISCONNECT
} }
ws.Close() ws.Close()
log.Infof("close rpc connection, %v", agent.Hostname) log.Infof("close rpc connection, %v", agent.Hostname)
// reset rpc client // reset rpc client
agent.rpc = nil agent.rpc = nil
}() }()
// set pong handler // set pong handler
ws.SetReadDeadline(time.Now().Add(PongWait)) ws.SetReadDeadline(time.Now().Add(PongWait))
ws.SetPongHandler(func(string) error { ws.SetPongHandler(func(string) error {
ws.SetReadDeadline(time.Now().Add(PongWait)) ws.SetReadDeadline(time.Now().Add(PongWait))
return nil return nil
}) })
// set ping goroutine to ping every PingWait time // set ping goroutine to ping every PingWait time
go func() { go func() {
ticker := time.NewTicker(PingWait) ticker := time.NewTicker(PingWait)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
if err := gs.wsping(ws, PongWait); err != nil { if err := gs.wsping(ws, PongWait); err != nil {
log.Errorf("rpc ping to %v failed: %v", agent.Hostname, err) log.Errorf("rpc ping to %v failed: %v", agent.Hostname, err)
break break
} }
} }
agent.closeRpcConnOnce() agent.closeRpcConnOnce()
}() }()
log.Infof("one rpc agent established, %v, cmdline: %v, pid: %v, hostname: %v", ws.RemoteAddr(), agent.CmdLine, agent.Pid, agent.Hostname) log.Infof("one rpc agent established, %v, cmdline: %v, pid: %v, hostname: %v", ws.RemoteAddr(), agent.CmdLine, agent.Pid, agent.Hostname)
// new rpc agent // new rpc agent
// 在这里 websocket server 作为 rpc 的客户端, // 在这里 websocket server 作为 rpc 的客户端,
// 发送 rpc 请求, // 发送 rpc 请求,
// 由被插桩服务返回 rpc 应答 // 由被插桩服务返回 rpc 应答
rwc := &ReadWriteCloser{ws: ws} rwc := &ReadWriteCloser{ws: ws}
codec := jsonrpc.NewClientCodec(rwc) codec := jsonrpc.NewClientCodec(rwc)
agent.rpc = rpc.NewClientWithCodec(codec) agent.rpc = rpc.NewClientWithCodec(codec)
// wait for exit // wait for exit
<-agent.exitCh <-agent.exitCh
} }
// generateAgentId generate id based on agent's meta infomation // generateAgentId generate id based on agent's meta infomation
func (gs *gocServer) generateAgentId(args ...string) gocCliendId { func (gs *gocServer) generateAgentId(args ...string) gocCliendId {
var path string var path string
for _, arg := range args { for _, arg := range args {
path += arg path += arg
} }
sum := sha256.Sum256([]byte(path)) sum := sha256.Sum256([]byte(path))
h := fmt.Sprintf("%x", sum[:6]) h := fmt.Sprintf("%x", sum[:6])
return gocCliendId(h) return gocCliendId(h)
} }

View File

@ -14,236 +14,236 @@
package server package server
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/rand" "math/rand"
"net/http" "net/http"
"net/rpc" "net/rpc"
"strconv" "strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/server/store" "github.com/ar0c/goc/v2/pkg/server/store"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
// gocServer represents a goc server // gocServer represents a goc server
type gocServer struct { type gocServer struct {
port int port int
store store.Store store store.Store
upgrader websocket.Upgrader upgrader websocket.Upgrader
agents sync.Map agents sync.Map
watchCh chan []byte watchCh chan []byte
watchClients sync.Map watchClients sync.Map
idCount int64 idCount int64
idL sync.Mutex idL sync.Mutex
} }
type gocCliendId string type gocCliendId string
const ( const (
DISCONNECT = 1 << iota DISCONNECT = 1 << iota
RPCCONNECT = 1 << iota RPCCONNECT = 1 << iota
WATCHCONNECT = 1 << iota WATCHCONNECT = 1 << iota
) )
// gocCoveredAgent represents a covered client // gocCoveredAgent represents a covered client
type gocCoveredAgent struct { type gocCoveredAgent struct {
Id string `json:"id"` Id string `json:"id"`
RpcRemoteIP string `json:"rpc_remoteip"` RpcRemoteIP string `json:"rpc_remoteip"`
WatchRemoteIP string `json:"watch_remoteip"` WatchRemoteIP string `json:"watch_remoteip"`
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
CmdLine string `json:"cmdline"` CmdLine string `json:"cmdline"`
Pid string `json:"pid"` Pid string `json:"pid"`
// 用户可以选择上报一些定制信息 // 用户可以选择上报一些定制信息
// 比如不同 namespace 的 statefulset POD它们的 hostname/cmdline/pid 都是一样的, // 比如不同 namespace 的 statefulset POD它们的 hostname/cmdline/pid 都是一样的,
// 这时候将 extra 设置为 namespace 并上报,这个额外的信息在展示时将更友好 // 这时候将 extra 设置为 namespace 并上报,这个额外的信息在展示时将更友好
Extra string `json:"extra"` Extra string `json:"extra"`
Token string `json:"token"` Token string `json:"token"`
Status int `json:"status"` // 表示该 agent 是否处于 connected 状态 Status int `json:"status"` // 表示该 agent 是否处于 connected 状态
rpc *rpc.Client `json:"-"` rpc *rpc.Client `json:"-"`
exitCh chan int `json:"-"` exitCh chan int `json:"-"`
closeRpcConnOnce func() `json:"-"` // close rpc conn 只执行一次 closeRpcConnOnce func() `json:"-"` // close rpc conn 只执行一次
closeWatchConnOnce func() `json:"-"` // close watch conn 只执行一次 closeWatchConnOnce func() `json:"-"` // close watch conn 只执行一次
} }
func (agent *gocCoveredAgent) closeConnection() { func (agent *gocCoveredAgent) closeConnection() {
if agent.closeRpcConnOnce != nil { if agent.closeRpcConnOnce != nil {
agent.closeRpcConnOnce() agent.closeRpcConnOnce()
} }
if agent.closeWatchConnOnce != nil { if agent.closeWatchConnOnce != nil {
agent.closeWatchConnOnce() agent.closeWatchConnOnce()
} }
} }
// api 客户端,不是 agent // api 客户端,不是 agent
type gocWatchClient struct { type gocWatchClient struct {
ws *websocket.Conn ws *websocket.Conn
exitCh chan int exitCh chan int
once sync.Once once sync.Once
} }
func RunGocServerUntilExit(host string, s store.Store) error { func RunGocServerUntilExit(host string, s store.Store) error {
gs := gocServer{ gs := gocServer{
store: s, store: s,
upgrader: websocket.Upgrader{ upgrader: websocket.Upgrader{
ReadBufferSize: 4096, ReadBufferSize: 4096,
WriteBufferSize: 4096, WriteBufferSize: 4096,
HandshakeTimeout: 45 * time.Second, HandshakeTimeout: 45 * time.Second,
CheckOrigin: func(r *http.Request) bool { CheckOrigin: func(r *http.Request) bool {
return true return true
}, },
}, },
watchCh: make(chan []byte, 4096), watchCh: make(chan []byte, 4096),
} }
// 从持久化存储上恢复 agent 列表 // 从持久化存储上恢复 agent 列表
gs.restoreAgents() gs.restoreAgents()
r := gin.Default() r := gin.Default()
v2 := r.Group("/v2") v2 := r.Group("/v2")
{ {
v2.GET("/cover/profile", gs.getProfiles) v2.GET("/cover/profile", gs.getProfiles)
v2.DELETE("/cover/profile", gs.resetProfiles) v2.DELETE("/cover/profile", gs.resetProfiles)
v2.GET("/agents", gs.listAgents) v2.GET("/agents", gs.listAgents)
v2.DELETE("/agents", gs.removeAgents) v2.DELETE("/agents", gs.removeAgents)
v2.GET("/cover/ws/watch", gs.watchProfileUpdate) v2.GET("/cover/ws/watch", gs.watchProfileUpdate)
// internal use only // internal use only
v2.GET("/internal/register", gs.register) v2.GET("/internal/register", gs.register)
v2.GET("/internal/ws/rpcstream", gs.serveRpcStream) v2.GET("/internal/ws/rpcstream", gs.serveRpcStream)
v2.GET("/internal/ws/watchstream", gs.serveWatchInternalStream) v2.GET("/internal/ws/watchstream", gs.serveWatchInternalStream)
} }
go gs.watchLoop() go gs.watchLoop()
return r.Run(host) return r.Run(host)
} }
func (gs *gocServer) register(c *gin.Context) { func (gs *gocServer) register(c *gin.Context) {
// 检查插桩服务上报的信息 // 检查插桩服务上报的信息
hostname := c.Query("hostname") hostname := c.Query("hostname")
pid := c.Query("pid") pid := c.Query("pid")
cmdline := c.Query("cmdline") cmdline := c.Query("cmdline")
extra := c.Query("extra") extra := c.Query("extra")
if hostname == "" || pid == "" || cmdline == "" { if hostname == "" || pid == "" || cmdline == "" {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"msg": "missing some params", "msg": "missing some params",
}) })
return return
} }
gs.idL.Lock() gs.idL.Lock()
gs.idCount++ gs.idCount++
globalId := gs.idCount globalId := gs.idCount
gs.idL.Unlock() gs.idL.Unlock()
genToken := func(i int64) string { genToken := func(i int64) string {
now := time.Now().UnixNano() now := time.Now().UnixNano()
random := rand.Int() random := rand.Int()
raw := fmt.Sprintf("%v-%v-%v", i, random, now) raw := fmt.Sprintf("%v-%v-%v", i, random, now)
sum := sha256.Sum256([]byte(raw)) sum := sha256.Sum256([]byte(raw))
h := fmt.Sprintf("%x", sum[:16]) h := fmt.Sprintf("%x", sum[:16])
return h return h
} }
token := genToken(globalId) token := genToken(globalId)
id := strconv.Itoa(int(globalId)) id := strconv.Itoa(int(globalId))
agent := &gocCoveredAgent{ agent := &gocCoveredAgent{
Id: id, Id: id,
Hostname: hostname, Hostname: hostname,
Pid: pid, Pid: pid,
CmdLine: cmdline, CmdLine: cmdline,
Token: token, Token: token,
Status: DISCONNECT, Status: DISCONNECT,
Extra: extra, Extra: extra,
} }
// 持久化 // 持久化
err := gs.saveAgentToStore(agent) err := gs.saveAgentToStore(agent)
if err != nil { if err != nil {
log.Errorf("fail to save to store: %v", err) log.Errorf("fail to save to store: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(), "msg": err.Error(),
}) })
} }
// 维护 agent 连接 // 维护 agent 连接
gs.agents.Store(id, agent) gs.agents.Store(id, agent)
log.Infof("one agent registered, id: %v, cmdline: %v, pid: %v, hostname: %v", id, agent.CmdLine, agent.Pid, agent.Hostname) log.Infof("one agent registered, id: %v, cmdline: %v, pid: %v, hostname: %v", id, agent.CmdLine, agent.Pid, agent.Hostname)
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"id": id, "id": id,
"token": token, "token": token,
}) })
} }
func (gs *gocServer) saveAgentToStore(agent *gocCoveredAgent) error { func (gs *gocServer) saveAgentToStore(agent *gocCoveredAgent) error {
value, err := json.Marshal(agent) value, err := json.Marshal(agent)
if err != nil { if err != nil {
return err return err
} }
return gs.store.Set("/goc/agents/"+agent.Id, string(value)) return gs.store.Set("/goc/agents/"+agent.Id, string(value))
} }
func (gs *gocServer) removeAgentFromStore(id string) error { func (gs *gocServer) removeAgentFromStore(id string) error {
return gs.store.Remove("/goc/agents/" + id) return gs.store.Remove("/goc/agents/" + id)
} }
func (gs *gocServer) removeAllAgentsFromStore() error { func (gs *gocServer) removeAllAgentsFromStore() error {
return gs.store.RangeRemove("/goc/agents/") return gs.store.RangeRemove("/goc/agents/")
} }
func (gs *gocServer) restoreAgents() { func (gs *gocServer) restoreAgents() {
pattern := "/goc/agents/" pattern := "/goc/agents/"
// ignore err, 这个 err 不需要处理,直接忽略 // ignore err, 这个 err 不需要处理,直接忽略
rawagents, _ := gs.store.Range(pattern) rawagents, _ := gs.store.Range(pattern)
var maxId int var maxId int
for _, rawagent := range rawagents { for _, rawagent := range rawagents {
var agent gocCoveredAgent var agent gocCoveredAgent
err := json.Unmarshal([]byte(rawagent), &agent) err := json.Unmarshal([]byte(rawagent), &agent)
if err != nil { if err != nil {
log.Fatalf("fail to unmarshal restore agents: %v", err) log.Fatalf("fail to unmarshal restore agents: %v", err)
} }
id, err := strconv.Atoi(agent.Id) id, err := strconv.Atoi(agent.Id)
if err != nil { if err != nil {
log.Fatalf("fail to transform id to number: %v", err) log.Fatalf("fail to transform id to number: %v", err)
} }
if maxId < id { if maxId < id {
maxId = id maxId = id
} }
gs.agents.Store(agent.Id, &agent) gs.agents.Store(agent.Id, &agent)
log.Infof("restore one agent: %v, %v from store", id, agent.RpcRemoteIP) log.Infof("restore one agent: %v, %v from store", id, agent.RpcRemoteIP)
agent.RpcRemoteIP = "" agent.RpcRemoteIP = ""
agent.WatchRemoteIP = "" agent.WatchRemoteIP = ""
agent.Status = DISCONNECT agent.Status = DISCONNECT
} }
// 更新全局 id // 更新全局 id
atomic.StoreInt64(&gs.idCount, int64(maxId)) atomic.StoreInt64(&gs.idCount, int64(maxId))
} }

View File

@ -14,120 +14,120 @@
package server package server
import ( import (
"net/http" "net/http"
"sync" "sync"
"time" "time"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
func (gs *gocServer) serveWatchInternalStream(c *gin.Context) { func (gs *gocServer) serveWatchInternalStream(c *gin.Context) {
// 检查插桩服务上报的信息 // 检查插桩服务上报的信息
watchRemoteIP, _ := c.RemoteIP() watchRemoteIP, _ := c.RemoteIP()
id := c.Query("id") id := c.Query("id")
token := c.Query("token") token := c.Query("token")
rawagent, ok := gs.agents.Load(id) rawagent, ok := gs.agents.Load(id)
if !ok { if !ok {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"msg": "agent not registered", "msg": "agent not registered",
"code": 1, "code": 1,
}) })
return return
} }
agent := rawagent.(*gocCoveredAgent) agent := rawagent.(*gocCoveredAgent)
if agent.Token != token { if agent.Token != token {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"msg": "register token not match", "msg": "register token not match",
"code": 1, "code": 1,
}) })
return return
} }
// 更新 agent 信息 // 更新 agent 信息
agent.WatchRemoteIP = watchRemoteIP.String() agent.WatchRemoteIP = watchRemoteIP.String()
agent.Status &= ^DISCONNECT // 取消 DISCONNECT 的状态 agent.Status &= ^DISCONNECT // 取消 DISCONNECT 的状态
agent.Status |= WATCHCONNECT // 设置为 RPC CONNECT 状态 agent.Status |= WATCHCONNECT // 设置为 RPC CONNECT 状态
var once sync.Once var once sync.Once
// upgrade to websocket // upgrade to websocket
ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil) ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
log.Errorf("fail to establish websocket connection with watch agent: %v", err) log.Errorf("fail to establish websocket connection with watch agent: %v", err)
c.JSON(http.StatusInternalServerError, nil) c.JSON(http.StatusInternalServerError, nil)
} }
// 注册销毁函数 // 注册销毁函数
agent.closeWatchConnOnce = func() { agent.closeWatchConnOnce = func() {
once.Do(func() { once.Do(func() {
// 关闭 ws 连接后ws.ReadMessage() 会出错退出 goroutine进入 defer // 关闭 ws 连接后ws.ReadMessage() 会出错退出 goroutine进入 defer
ws.Close() ws.Close()
}) })
} }
// send close msg and close ws connection // send close msg and close ws connection
defer func() { defer func() {
// 取消 WATCH CONNECT 状态 // 取消 WATCH CONNECT 状态
agent.Status &= ^WATCHCONNECT agent.Status &= ^WATCHCONNECT
if agent.Status == 0 { if agent.Status == 0 {
agent.Status = DISCONNECT agent.Status = DISCONNECT
} }
agent.closeWatchConnOnce() agent.closeWatchConnOnce()
log.Infof("close watch connection, %v", agent.Hostname) log.Infof("close watch connection, %v", agent.Hostname)
}() }()
// set pong handler // set pong handler
ws.SetReadDeadline(time.Now().Add(PongWait)) ws.SetReadDeadline(time.Now().Add(PongWait))
ws.SetPongHandler(func(string) error { ws.SetPongHandler(func(string) error {
ws.SetReadDeadline(time.Now().Add(PongWait)) ws.SetReadDeadline(time.Now().Add(PongWait))
return nil return nil
}) })
// set ping goroutine to ping every PingWait time // set ping goroutine to ping every PingWait time
go func() { go func() {
ticker := time.NewTicker(PingWait) ticker := time.NewTicker(PingWait)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
if err := gs.wsping(ws, PongWait); err != nil { if err := gs.wsping(ws, PongWait); err != nil {
log.Errorf("watch ping to %v failed: %v", agent.Hostname, err) log.Errorf("watch ping to %v failed: %v", agent.Hostname, err)
break break
} }
} }
}() }()
log.Infof("one watch agent established, %v, cmdline: %v, pid: %v, hostname: %v", ws.RemoteAddr(), agent.CmdLine, agent.Pid, agent.Hostname) log.Infof("one watch agent established, %v, cmdline: %v, pid: %v, hostname: %v", ws.RemoteAddr(), agent.CmdLine, agent.Pid, agent.Hostname)
for { for {
mt, message, err := ws.ReadMessage() mt, message, err := ws.ReadMessage()
if err != nil { if err != nil {
log.Errorf("read from %v: %v", agent.Hostname, err) log.Errorf("read from %v: %v", agent.Hostname, err)
break break
} }
if mt == websocket.TextMessage { if mt == websocket.TextMessage {
gs.watchCh <- message gs.watchCh <- message
} }
} }
} }
func (gs *gocServer) watchLoop() { func (gs *gocServer) watchLoop() {
for { for {
msg := <-gs.watchCh msg := <-gs.watchCh
gs.watchClients.Range(func(key, value interface{}) bool { gs.watchClients.Range(func(key, value interface{}) bool {
// 这里是客户端的 ws 连接,不是 agent ws 连接 // 这里是客户端的 ws 连接,不是 agent ws 连接
gwc := value.(*gocWatchClient) gwc := value.(*gocWatchClient)
err := gwc.ws.WriteMessage(websocket.TextMessage, msg) err := gwc.ws.WriteMessage(websocket.TextMessage, msg)
if err != nil { if err != nil {
gwc.ws.Close() gwc.ws.Close()
gwc.once.Do(func() { close(gwc.exitCh) }) gwc.once.Do(func() { close(gwc.exitCh) })
} }
return true return true
}) })
} }
} }

View File

@ -14,26 +14,26 @@
package watch package watch
import ( import (
"fmt" "fmt"
"github.com/RickLeee/goc/v2/pkg/log" "github.com/ar0c/goc/v2/pkg/log"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
func Watch(host string) { func Watch(host string) {
watchUrl := fmt.Sprintf("ws://%v/v2/cover/ws/watch", host) watchUrl := fmt.Sprintf("ws://%v/v2/cover/ws/watch", host)
c, _, err := websocket.DefaultDialer.Dial(watchUrl, nil) c, _, err := websocket.DefaultDialer.Dial(watchUrl, nil)
if err != nil { if err != nil {
log.Fatalf("cannot connect to goc server: %v", err) log.Fatalf("cannot connect to goc server: %v", err)
} }
defer c.Close() defer c.Close()
for { for {
_, message, err := c.ReadMessage() _, message, err := c.ReadMessage()
if err != nil { if err != nil {
log.Fatalf("cannot read message: %v", err) log.Fatalf("cannot read message: %v", err)
} }
log.Infof("profile update: %v", string(message)) log.Infof("profile update: %v", string(message))
} }
} }