Merge pull request 'Create Golang version devtool' (#7) from oscar-next into master

Reviewed-on: #7
This commit is contained in:
oscar 2022-12-29 20:34:22 +11:00
commit 927d6e92b0
26 changed files with 1120 additions and 0 deletions

1
go/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/test

18
go/commands/git.go Normal file
View File

@ -0,0 +1,18 @@
package commands
import (
"ocl/portainer-devtool/utils"
"os/exec"
)
func ListBranches(workdir string) error {
cmd := exec.Command("git", "branch")
cmd.Dir = workdir
out, err := cmd.Output()
if err != nil {
return err
}
utils.PrintOutput("Your current checkout branch", out)
return nil
}

15
go/commands/yarn.go Normal file
View File

@ -0,0 +1,15 @@
package commands
import (
"ocl/portainer-devtool/utils"
)
// RunClient starts the portainer client
func RunPortainerClient(workdir string) error {
err := utils.RunCommandWithStdoutPipe(workdir, "yarn")
if err != nil {
return err
}
return utils.RunCommandWithStdoutPipe(workdir, "yarn", "start:client")
}

153
go/configs/config.go Normal file
View File

@ -0,0 +1,153 @@
package configs
import (
"encoding/json"
"errors"
"fmt"
"io"
"ocl/portainer-devtool/utils"
"os"
)
const (
ConfigFileName string = ".devtool"
)
var (
ErrConfigNotInitialized error = errors.New("Config file is not initialized")
)
type Config struct {
// ProjectPath is the location on your host where all dev relevant folders will be stored to
ProjectPath string
// VolumePath is where all the persisitant data will be stored
VolumePath string
// Credentials for UI login
LoginCredential LoginCredential
// key is repository name, for example, "repository-ee"
RepositoryConfig map[string]RepositoryConfig
}
// LoginCredential stores the user credential for API request
type LoginCredential struct {
Username string
Password string
Address string
}
type RepositoryConfig struct {
Name string
URL string
Directory string
Private bool
GitUsername string
GitPassword string
}
func GetConfig() (*Config, error) {
file, err := getConfigFile(ConfigFileName)
if err != nil {
if err == ErrConfigNotInitialized {
return initializeConfig(file)
}
return nil, err
}
defer file.Close()
return getConfig(file)
}
func (config *Config) Summarize() {
fmt.Printf("The project path is %s\nThe volume path is %s\n", config.ProjectPath, config.VolumePath)
if config.LoginCredential.Username != "" && config.LoginCredential.Password != "" {
fmt.Printf("Login credential [%s] is configured\n", config.LoginCredential.Username)
}
if len(config.RepositoryConfig) > 0 {
for name := range config.RepositoryConfig {
fmt.Printf("Repository [%s] is added\n", name)
}
} else {
fmt.Println("No repository is added")
}
}
// initializeConfig will set up the mandatory dev information for the first time.
// such as devtool path, login credential
// The configuration also can be updated later
func initializeConfig(w io.WriteCloser) (*Config, error) {
config := &Config{}
config.ProjectPath = utils.Prompt("Specify Git Project Root Path")
// analyze all the repositories in the project root path
// add the parsed information to RepositoryConfig
config.configureRepositories()
// generate volume path automatically
config.VolumePath = utils.Prompt("Specify Volume Path")
config.configureLoginCredential()
// able to configure multiple project
// if utils.PromptConfirm("Do you want to configure the repository now?") {
// // configure repository
// }
bytes, err := json.Marshal(config)
if err != nil {
return nil, err
}
_, err = w.Write(bytes)
if err != nil {
return nil, err
}
return config, nil
}
func getConfig(f *os.File) (*Config, error) {
config := &Config{}
info, err := f.Stat()
if err != nil {
return nil, err
}
bytes := make([]byte, info.Size())
n, err := f.Read(bytes)
if err != nil {
return nil, err
}
if n == 0 {
// The file exists, but it's empty file, so we need to initalize
return initializeConfig(f)
}
err = json.Unmarshal(bytes, &config)
if err != nil {
return nil, err
}
return config, nil
}
// getConfigFile get the config file handler
func getConfigFile(name string) (*os.File, error) {
_, err := os.Stat(name)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
//create file
file, err := os.Create(name)
if err != nil {
return nil, fmt.Errorf("fail to create config file: %w", err)
}
// first set up the git project path and volume path
// git credential
return file, err
} else {
return nil, err
}
}
return os.OpenFile(name, os.O_RDWR, 0644)
}

32
go/configs/credential.go Normal file
View File

@ -0,0 +1,32 @@
package configs
import (
"fmt"
"ocl/portainer-devtool/utils"
)
func (config *Config) configureLoginCredential() {
var loginCredential LoginCredential
loginCredential.Username = utils.Prompt("Set Login Credential Username(admin)")
if loginCredential.Username == "" {
loginCredential.Username = "admin"
}
for {
loginCredential.Password = utils.Prompt("Set Login Credential Password(*****)")
if loginCredential.Password != "" {
break
}
utils.WarnPrint("Login Credential Password must be provided")
}
loginCredential.Address = utils.Prompt("Set Login Address(127.0.0.1)")
if loginCredential.Address == "" {
loginCredential.Address = "http://127.0.0.1:9000/api/auth"
} else {
loginCredential.Address = fmt.Sprintf("http://%s:9000/api/auth", loginCredential.Address)
}
config.LoginCredential = loginCredential
}

54
go/configs/repository.go Normal file
View File

@ -0,0 +1,54 @@
package configs
import (
"fmt"
"io/fs"
"log"
"ocl/portainer-devtool/utils"
"path/filepath"
)
func (config *Config) configureRepositories() {
if config.RepositoryConfig == nil {
config.RepositoryConfig = make(map[string]RepositoryConfig)
}
for {
if !utils.PromptConfirm("Set up new repository") {
break
}
repoConfig := RepositoryConfig{}
repoConfig.Name = utils.Prompt("Name")
repoConfig.URL = utils.Prompt("URL")
repoConfig.Directory = utils.Prompt("Directory")
config.RepositoryConfig[repoConfig.Name] = repoConfig
}
utils.HighlightPrint("Configure repositories completed")
}
func (config *Config) generateRepositoriesBasedOnProjectPath(projectPath string) error {
filepath.WalkDir(projectPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
log.Printf("fail to walk in the project path %s, error: %v\n", projectPath, err)
return err
}
if utils.MatchPathLength(projectPath, path, 1) {
fmt.Println(path)
// posLastSeparator := strings.LastIndex(path, string(filepath.Separator))
// repoName := path[posLastSeparator+1:]
// repoConfig := RepositoryConfig{
// Name: repoName,
// // URL:
// }
// config.RepositoryConfig[repoName] =
}
return nil
})
return nil
}

3
go/go.mod Normal file
View File

@ -0,0 +1,3 @@
module ocl/portainer-devtool
go 1.18

26
go/main.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
"log"
"ocl/portainer-devtool/configs"
"ocl/portainer-devtool/tasks"
)
func main() {
config, err := configs.GetConfig()
if err != nil {
log.Fatalln(err)
}
config.Summarize()
// Init tasks
taskItems := []tasks.Tasker{
tasks.NewGenerateJwtTokenTask(config),
tasks.NewCurlLookupTask(),
tasks.NewCodeSecurityScanTask(),
tasks.NewListDevToolCommandTask(config),
}
tasks.ListCommandMenu(taskItems, "Which repository of action do you want operate:")
}

View File

@ -0,0 +1,5 @@
package repositories
type Actioner interface {
Execute() error
}

71
go/repositories/ee.go Normal file
View File

@ -0,0 +1,71 @@
package repositories
import (
"fmt"
"ocl/portainer-devtool/commands"
"ocl/portainer-devtool/utils"
)
const (
ACTION_EE_BUILD_ALL int = iota + 1
ACTION_EE_RUN_FRONTEND
ACTION_EE_RUN_BACKEND
ACTION_EE_VALIDATE_ALL
ACTION_EE_VALIDATE_FRONTEND
ACTION_EE_VALIDATE_BACKEND
ACTION_EE_RUN_UNIT_TEST_ALL
ACTION_EE_RUN_UNIT_TEST_FRONTEND
ACTION_EE_RUN_UNIT_TEST_BACKEND
)
type PortainerEE struct {
WorkDir string
FrontendDir string
BackendDir string
}
func NewPortainerEERepository() *PortainerEE {
repo := &PortainerEE{
WorkDir: "/home/oscarzhou/source/github.com/portainer/portainer-ee",
}
utils.HighlightPrint("Your portainer EE repository work directory is ")
fmt.Println(repo.WorkDir)
return repo
}
func (repo *PortainerEE) Execute() error {
err := commands.ListBranches(repo.WorkDir)
if err != nil {
return err
}
if !utils.PromptContinue() {
return nil
}
option := utils.PromptMenu(repo.listSubMenu)
switch option {
case ACTION_EE_BUILD_ALL:
case ACTION_EE_RUN_FRONTEND:
commands.RunPortainerClient(repo.WorkDir)
}
return nil
}
func (repo *PortainerEE) listSubMenu() {
utils.MenuPrint("Do you want?", `
1. Build both front-end and backend
2. Run front-end only
3. Run backend only
4. Validate both fornt-end and backend before commit
5. Validate front-end only before commit
6. Validate backend only before commit
7. Run unit tests for both front-end and backend
8. Run unit tests for front-end only
9. Run unit tests for backend only`)
}

16
go/tasks/build_all.go Normal file
View File

@ -0,0 +1,16 @@
package tasks
import "ocl/portainer-devtool/configs"
type BuildAllTask struct {
Config *configs.Config
}
func (task *BuildAllTask) Execute() error {
return nil
}
func (task *BuildAllTask) String() string {
return "Build all"
}

15
go/tasks/build_backend.go Normal file
View File

@ -0,0 +1,15 @@
package tasks
import "ocl/portainer-devtool/configs"
type BuildBackendOnlyTask struct {
Config *configs.Config
}
func (task *BuildBackendOnlyTask) Execute() error {
return nil
}
func (task *BuildBackendOnlyTask) String() string {
return "Build backend only"
}

View File

@ -0,0 +1,35 @@
package tasks
import (
"ocl/portainer-devtool/utils"
)
type CodeSecurityScanTask struct {
}
func NewCodeSecurityScanTask() *CodeSecurityScanTask {
return &CodeSecurityScanTask{}
}
func (task *CodeSecurityScanTask) Execute() error {
utils.SuccessPrint(`
1. Scan client with snyk: "snyk test"
2. Scan server with snyk: "cd api && snyk test"
3. If snyk is not authenticated: "snyk auth"
4. Specify the severity threshold: "snyk test --severity-threshold=<low|medium|high|critical>"
5. Other commands with snyk: "snyk --help"
`)
utils.SuccessPrint(`
Steps to scan portainer image with Trivy:
1. Build the local image: "docker build -t oscarzhou/portainer:dev-ee -f build/linux/Dockfile ."
2. Scan with trivy: 'docker run --rm -v "/var/run/docker.sock":"/var/run/docker.sock" aquasec/trivy:latest image oscarzhou/portainer:dev-ee'
3. Other commands with trivy: 'docker run --rm -v "/var/run/docker.sock":"/var/run/docker.sock" aquasec/trivy:latest --help'
`)
return nil
}
func (task *CodeSecurityScanTask) String() string {
return "Code Security Scan"
}

48
go/tasks/curl_lookup.go Normal file
View File

@ -0,0 +1,48 @@
package tasks
import (
"fmt"
"ocl/portainer-devtool/utils"
)
type CurlLookupTask struct {
}
func NewCurlLookupTask() *CurlLookupTask {
return &CurlLookupTask{}
}
func (task *CurlLookupTask) Execute() error {
var option string
utils.InputPrint("1.POST 2.GET 3.PUT 4.DELETE: ")
fmt.Scanf("%s", &option)
switch option {
case "1", "POST", "post":
utils.HighlightPrint("POST Command:")
utils.SuccessPrint("curl -d '{\"repository\":\"https://github.com/portainer/portainer-ee\",\"username\":\"oscarzhou\", \"password\":\"your PAT\"}' -H 'Content-Type: application/json' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsInNjb3BlIjoiZGVmYXVsdCIsImZvcmNlQ2hhbmdlUGFzc3dvcmQiOmZhbHNlLCJleHAiOjE2NjAwMzQ2MjUsImlhdCI6MTY2MDAwNTgyNX0.S0UbPO4POD9kbuWOmvO9WR6LY6v424bpGw46rlEkNs0' http://127.0.0.1:9000/api/gitops/repo/refs")
break
case "2", "GET", "get":
utils.HighlightPrint("GET Command:")
utils.SuccessPrint("curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsInNjb3BlIjoiZGVmYXVsdCIsImZvcmNlQ2hhbmdlUGFzc3dvcmQiOmZhbHNlLCJleHAiOjE2NTUxMTg2ODUsImlhdCI6MTY1NTA4OTg4NX0.mJSZomeiEpRlz36MxSsLFWpUbA0BHRXWYijsZAo1NWc' http://127.0.0.1:9000/api/users/1/gitcredentials")
break
case "3", "PUT", "put":
utils.HighlightPrint("PUT Command:")
utils.SuccessPrint(`curl -X PUT http://127.0.0.1:9000/api/users/1/gitcredentials/11 -d '{"name":"test-credential-11","username":"cred11", "password":"cred11"}' -H 'Content-Type: application/json' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsInNjb3BlIjoiZGVmYXVsdCIsImZvcmNlQ2hhbmdlUGFzc3dvcmQiOmZhbHNlLCJleHAiOjE2NTcwODQ5MzUsImlhdCI6MTY1NzA1NjEzNX0.kUhkhhSt4WH33Q3hYzLwsYDv1a9a2ygCi6p8MkKMbwc'`)
break
case "4", "DELETE", "delete":
utils.HighlightPrint("DELETE Command:")
utils.SuccessPrint(`curl -X DELETE http://192.168.1.109:9000/api/users/1/gitcredentials/1 -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsInNjb3BlIjoiZGVmYXVsdCIsImZvcmNlQ2hhbmdlUGFzc3dvcmQiOmZhbHNlLCJleHAiOjE2NTQ3NTc1NzYsImlhdCI6MTY1NDcyODc3Nn0.GlxGmL6XTTH29Ns8aRnX5qp1qBfDVF2zaPzuSmG7qUs'`)
break
default:
return fmt.Errorf("No option %v\n", option)
}
return nil
}
func (task *CurlLookupTask) String() string {
return "Lookup Curl Commands"
}

18
go/tasks/exit.go Normal file
View File

@ -0,0 +1,18 @@
package tasks
import "errors"
type ExitTask struct {
}
func NewExitTask() *ExitTask {
return &ExitTask{}
}
func (task *ExitTask) Execute() error {
return errors.New("exit")
}
func (task *ExitTask) String() string {
return "Exit"
}

57
go/tasks/jwt_token_gen.go Normal file
View File

@ -0,0 +1,57 @@
package tasks
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"ocl/portainer-devtool/configs"
)
type GenerateJwtTokenTask struct {
Config *configs.Config
}
type GenerateJwtTokenResponse struct {
JWT string `json:"jwt"`
}
func NewGenerateJwtTokenTask(cfg *configs.Config) *GenerateJwtTokenTask {
return &GenerateJwtTokenTask{
Config: cfg,
}
}
func (task *GenerateJwtTokenTask) Execute() error {
postBody, _ := json.Marshal(map[string]string{
"username": task.Config.LoginCredential.Username,
"password": task.Config.LoginCredential.Password,
})
responseBody := bytes.NewBuffer(postBody)
resp, err := http.Post(task.Config.LoginCredential.Address, "application/json", responseBody)
if err != nil {
return fmt.Errorf("http requset error: %s", err.Error())
}
defer resp.Body.Close()
//Read the response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to parse the response body: %s", err.Error())
}
var ret GenerateJwtTokenResponse
err = json.Unmarshal(body, &ret)
if err != nil {
return err
}
fmt.Printf("jwt token is:\n%s\n", ret.JWT)
return nil
}
func (task *GenerateJwtTokenTask) String() string {
return "Generate JWT Token"
}

View File

@ -0,0 +1,31 @@
package tasks
import (
"ocl/portainer-devtool/configs"
"ocl/portainer-devtool/tasks/subtasks"
)
type ListDevToolCommandTask struct {
Config *configs.Config
}
func NewListDevToolCommandTask(cfg *configs.Config) *ListDevToolCommandTask {
return &ListDevToolCommandTask{
Config: cfg,
}
}
func (task *ListDevToolCommandTask) Execute() error {
subTaskItems := []Tasker{
subtasks.NewListVolumeSubTask(task.Config),
subtasks.NewListRepositorySubTask(task.Config),
}
ListCommandMenu(subTaskItems, "Which management commands do you want to choose:")
return nil
}
func (task *ListDevToolCommandTask) String() string {
return "List Dev Tool Commands"
}

View File

@ -0,0 +1,34 @@
package subtasks
import (
"ocl/portainer-devtool/configs"
"ocl/portainer-devtool/utils"
"strings"
)
type ListRepositorySubTask struct {
Config *configs.Config
}
func NewListRepositorySubTask(cfg *configs.Config) *ListRepositorySubTask {
return &ListRepositorySubTask{
Config: cfg,
}
}
func (task *ListRepositorySubTask) Execute() error {
utils.InputPrint("Which repository?")
repositoryList := []string{" "}
for _, repo := range task.Config.RepositoryConfig {
repositoryList = append(repositoryList, repo.Name)
}
utils.SuccessPrint(strings.Join(repositoryList, "\n"))
return nil
}
func (task *ListRepositorySubTask) String() string {
return "List Repositories"
}

View File

@ -0,0 +1,51 @@
package subtasks
import (
"fmt"
"io/fs"
"ocl/portainer-devtool/configs"
"ocl/portainer-devtool/utils"
"path/filepath"
"strings"
)
type ListVolumeSubTask struct {
Config *configs.Config
}
func NewListVolumeSubTask(cfg *configs.Config) *ListVolumeSubTask {
return &ListVolumeSubTask{
Config: cfg,
}
}
func (task *ListVolumeSubTask) Execute() error {
utils.HighlightPrint(fmt.Sprintf("Volume path: %s", task.Config.VolumePath))
volumeList := []string{" "}
filepath.WalkDir(task.Config.VolumePath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if path == task.Config.VolumePath {
return nil
}
if d.IsDir() {
if utils.MatchPathLength(task.Config.VolumePath, path, 1) {
volumeList = append(volumeList, d.Name())
}
}
return nil
})
utils.SuccessPrint(strings.Join(volumeList, "\n"))
return nil
}
func (task *ListVolumeSubTask) String() string {
return "List Volumes"
}

54
go/tasks/tasker.go Normal file
View File

@ -0,0 +1,54 @@
package tasks
import (
"fmt"
"ocl/portainer-devtool/utils"
"os"
"strconv"
)
type Tasker interface {
Execute() error
String() string
}
// ListCommandMenu iterates task items to display them // on the screen as the menu options
func ListCommandMenu(taskItems []Tasker, menuDesp string) error {
taskItems = append(taskItems, NewExitTask())
for {
printMainMenu := func() {
taskNames := []string{}
for _, task := range taskItems {
taskNames = append(taskNames, task.String())
}
utils.PrintMenu(menuDesp, taskNames)
// utils.MenuPrint("Which repository or action do you want to operate:", `
// 1. Portainer EE Repository
// 2. Portainer CE Repository
// 3. Portainer Agent Repository
// 4. Others
// 5. Quit`)
}
option := utils.SelectMenuItem(printMainMenu)
index, err := strconv.Atoi(option)
if err != nil {
utils.ErrorPrint("please type the option number\n")
continue
}
if index < 1 || index > len(taskItems) {
utils.ErrorPrint(fmt.Sprintf("no such option %s, please select again\n", option))
continue
}
err = taskItems[index-1].Execute()
if err != nil {
utils.ErrorPrint(err.Error())
os.Exit(1)
}
}
}

45
go/utils/command.go Normal file
View File

@ -0,0 +1,45 @@
package utils
import (
"bufio"
"fmt"
"os/exec"
)
func RunCommandWithStdoutPipe(workdir, progName string, args ...string) error {
cmd := exec.Command(progName, args...)
cmd.Dir = workdir
out, err := cmd.StdoutPipe()
if err != nil {
return err
}
scanner := bufio.NewScanner(out)
go func() {
counter := 0
for scanner.Scan() {
if counter > 10 {
// Swallow the output
if counter%50 == 0 {
fmt.Printf("output %d lines in total.\n", counter)
}
counter++
continue
}
PrintOutput("", scanner.Bytes())
counter++
}
}()
err = cmd.Start()
if err != nil {
return err
}
err = cmd.Wait()
if err != nil {
return err
}
return nil
}

18
go/utils/path.go Normal file
View File

@ -0,0 +1,18 @@
package utils
import (
"path/filepath"
"strings"
)
// MatchPathLength matches the length of target path separated by path separator
// to the length of base path separated by path separator plus offset
func MatchPathLength(basePath, targetPath string, offset int) bool {
basePathLength := len(strings.Split(basePath, string(filepath.Separator)))
targetPathLength := len(strings.Split(targetPath, string(filepath.Separator)))
if basePathLength+offset == targetPathLength {
return true
}
return false
}

87
go/utils/print.go Normal file
View File

@ -0,0 +1,87 @@
package utils
import (
"fmt"
)
const (
colorReset string = "\033[0m"
colorRed string = "\033[31m"
colorGreen string = "\033[32m"
colorYellow string = "\033[33m"
colorBlue string = "\033[34m"
colorPurple string = "\033[35m"
colorCyan string = "\033[36m"
colorWhite string = "\033[37m"
)
func PrintOutput(message string, output []byte) {
if message != "" {
HighlightPrint(message)
}
fmt.Println(string(output))
}
func HighlightPrint(message string) {
fmt.Println()
fmt.Println(colorBlue, message, colorReset)
}
func SuccessPrint(message string) {
fmt.Println()
fmt.Println(colorGreen, message, colorReset)
}
func ErrorPrint(message string) {
fmt.Println()
fmt.Println(colorRed, message, colorReset)
}
func WarnPrint(message string) {
fmt.Println()
fmt.Println(colorPurple, message, colorReset)
}
func InputPrint(message string) {
// adding \n before setting colorful output can
// remove the first space in the colorful output
fmt.Println()
fmt.Println(colorYellow, message, colorReset)
}
func PrintMenu(question string, taskNames []string) {
if question != "" {
InputPrint(fmt.Sprintf("[%s]", question))
}
// adding \n before setting colorful output can
// remove the first space in the colorful output
menuContent := "\n"
for i, name := range taskNames {
menuContent += fmt.Sprintf("%d. %s\n", i+1, name)
}
fmt.Println(colorCyan, menuContent, colorReset)
}
func MenuPrint(question, menu string) {
if question != "" {
InputPrint(fmt.Sprintf("[%s]", question))
}
// menu := `
// 1. Build Portainer EE/CE All
// 2. Build Portainer EE/CE Frontend
// 3. Build Portainer EE/CE Backend
// 4. Generate Portainer EE/CE JWT
// 5. Run Before Commit [Portainer EE/CE]
// 6. Get Portainer CE API Reference
// 7. Run Before Commit [k8s]
// 8. Build Portainer Agent
// 9. Cleanup Temporary Volume
// `
fmt.Println(colorCyan, menu, colorReset)
}

39
go/utils/prompt.go Normal file
View File

@ -0,0 +1,39 @@
package utils
import (
"fmt"
"strings"
)
func PromptContinue() bool {
ret := strings.ToLower(Prompt("Continue (y/n)"))
if ret == "y" || ret == "yes" {
return true
}
return false
}
func PromptConfirm(question string) bool {
ret := Prompt(fmt.Sprintf("%s (y/n)?", question))
if ret == "y" || ret == "yes" {
return true
}
return false
}
func SelectMenuItem(listMenu func()) string {
listMenu()
var option string
fmt.Scanf("%s", &option)
return option
}
func Prompt(question string) string {
fmt.Printf("%s %s :%s", colorYellow, question, colorReset)
var ret string
fmt.Scanf("%s", &ret)
return ret
}

183
run.sh Executable file
View File

@ -0,0 +1,183 @@
#!/bin/bash
set -eu
source ./utils/common.sh
WORKDIR=/home/oscarzhou/source/github.com/portainer
GLOBAL_VOLUME=/home/oscarzhou/volumes
TRUE=0;
FALSE=1;
REPO_DIR=
REPO_VOLUME=
function debug_portainer_client() {
print_highlight "[debug portainer client]"
yarn
yarn start:client
}
function generate_portainer_jwt_token() {
print_highlight "[generate portainer jwt token]"
read -p "Username(admin):" username
if [ -z "$username" ]; then
username="admin";
fi
read -p "Password(****):" password
read -p "Address(http://127.0.0.1:9000):" address
if [ -z "$address" ]; then
address="http://127.0.0.1:9000";
fi
payload="{\"username\":\"${username}\",\"password\":\"${password}\"}"
curl -d ${payload} -H 'Content-Type: application/json' "${address}/api/auth"
}
function list_portainer_ee_menu() {
print_highlight "Your current working directory is ${WORKDIR}/portainer-ee"
if ! prompt_continue; then
exit;
fi
REPO_DIR=${WORKDIR}/portainer-ee
print_highlight "Your current volume is ${VOLUME}/portainer-ee-data"
if ! prompt_continue; then
exit;
fi
REPO_VOLUME=${VOLUME}/portainer-ee-data
PS3='Please select the action: '
OPTIONS=(
'Debug Client'
'Lint Client'
'Run Unit Test for Client'
'Before Commit'
'Build Client'
'Build Server'
'Run Unit Test for Server'
'Get Portainer CE API Reference'
'Quit'
)
select opt in "${OPTIONS[@]}"
do
case $opt in
'Debug Client')
debug_portainer_client
;;
'PortainerCE')
build_portainer_frontend
;;
'Build Portainer EE/CE Backend')
build_portainer_backend
;;
'Generate Portainer EE/CE JWT')
generate_portainer_jwt
;;
'Run Before Commit [Portainer EE/CE]')
run_before_commit
;;
'Get Portainer CE API Reference')
get_portainer_ce_api_reference
;;
'Quit')
break
;;
esac
done
}
function code_security_scan_summary() {
echo "
1. Scan client with snyk: $(print_highlight "snyk test")
2. Scan server with snyk: $(print_highlight "cd api && snyk test")
3. If snyk is not authenticated: $(print_highlight "snyk auth")
4. Specify the severity threshold: $(print_highlight "snyk test --severity-threshold=<low|medium|high|critical>")
5. Other commands with snyk: $(print_highlight "snyk --help")
"
echo "
Steps to scan portainer image with Trivy:
1. Build the local image: $(print_highlight "docker build -t oscarzhou/portainer:dev-ee -f build/linux/Dockfile .")
2. Scan with trivy: $(print_highlight 'docker run --rm -v "/var/run/docker.sock":"/var/run/docker.sock" aquasec/trivy:latest image oscarzhou/portainer:dev-ee')
3. Other commands with trivy: $(print_highlight 'docker run --rm -v "/var/run/docker.sock":"/var/run/docker.sock" aquasec/trivy:latest --help')
"
}
function look_up_curl_commands() {
input "1.POST 2.GET 3.PUT 4.DELETE :" option
if [[ "${option}" == "1" ]]; then
echo "$(print_highlight "curl -d '{\"repository\":\"https://github.com/portainer/portainer-ee\",\"username\":\"oscarzhou\", \"password\":\"your PAT\"}' -H 'Content-Type: application/json' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsInNjb3BlIjoiZGVmYXVsdCIsImZvcmNlQ2hhbmdlUGFzc3dvcmQiOmZhbHNlLCJleHAiOjE2NjAwMzQ2MjUsImlhdCI6MTY2MDAwNTgyNX0.S0UbPO4POD9kbuWOmvO9WR6LY6v424bpGw46rlEkNs0' http://127.0.0.1:9000/api/gitops/repo/refs")"
elif [[ "${option}" == "2" ]]; then
echo "$(print_highlight "curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsInNjb3BlIjoiZGVmYXVsdCIsImZvcmNlQ2hhbmdlUGFzc3dvcmQiOmZhbHNlLCJleHAiOjE2NTUxMTg2ODUsImlhdCI6MTY1NTA4OTg4NX0.mJSZomeiEpRlz36MxSsLFWpUbA0BHRXWYijsZAo1NWc' http://127.0.0.1:9000/api/users/1/gitcredentials")"
elif [[ "${option}" == "3" ]]; then
echo "$(print_highlight "curl -X PUT http://127.0.0.1:9000/api/users/1/gitcredentials/11 -d '{"name":"test-credential-11","username":"cred11", "password":"cred11"}' -H 'Content-Type: application/json' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsInNjb3BlIjoiZGVmYXVsdCIsImZvcmNlQ2hhbmdlUGFzc3dvcmQiOmZhbHNlLCJleHAiOjE2NTcwODQ5MzUsImlhdCI6MTY1NzA1NjEzNX0.kUhkhhSt4WH33Q3hYzLwsYDv1a9a2ygCi6p8MkKMbwc'")"
elif [[ "${option}" == "4" ]]; then
echo "$(print_highlight "curl -X DELETE http://192.168.1.109:9000/api/users/1/gitcredentials/1 -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsInNjb3BlIjoiZGVmYXVsdCIsImZvcmNlQ2hhbmdlUGFzc3dvcmQiOmZhbHNlLCJleHAiOjE2NTQ3NTc1NzYsImlhdCI6MTY1NDcyODc3Nn0.GlxGmL6XTTH29Ns8aRnX5qp1qBfDVF2zaPzuSmG7qUs'")"
else
print_error "Invalid option"
fi
}
function menu() {
PS3='Please select the action/repository: '
OPTIONS=(
'PortainerEE'
'PortainerCE'
'Build Portainer EE/CE Backend'
'Generate Portainer JWT Token'
'Run Before Commit [Portainer EE/CE]'
'Get Portainer CE API Reference'
'Look Up Curl Commands'
'Code Security Scan'
'Cleanup Temporary Volume'
'Quit'
)
select opt in "${OPTIONS[@]}"
do
case $opt in
'PortainerEE')
list_portainer_ee_menu
;;
'PortainerCE')
build_portainer_frontend
;;
'Build Portainer EE/CE Backend')
build_portainer_backend
;;
'Generate Portainer JWT Token')
generate_portainer_jwt
;;
'Run Before Commit [Portainer EE/CE]')
run_before_commit
;;
'Get Portainer CE API Reference')
get_portainer_ce_api_reference
;;
'Look Up Curl Commands')
look_up_curl_commands
;;
'Code Security Scan')
code_security_scan_summary
;;
'Cleanup Temporary Volume')
cleanup_temporary_volume
;;
'Quit')
break
;;
esac
done
}
# check if the function exists (bash specific)
if [ "$#" -eq 0 ]; then
menu
else
"$@"
fi

View File

@ -1,5 +1,7 @@
#!/bin/bash
TRUE=0;
FALSE=1;
ERROR_COLOR='\033[0;31m';
HIGHLIGHT_COLOR='\033[0;32m';
@ -18,3 +20,12 @@ function print_error() {
function input() {
read -p "$(echo -e ${INPUT_COLOR}$1 ${NO_COLOR})" $2
}
function prompt_continue() {
read -p "Continue N/(Y)?" is_continue
if [[ "${is_continue}" == "N" || "${is_continue}" == "n" ]]; then
return $FALSE;
fi
return $TRUE;
}