Compare commits

..

42 Commits

Author SHA1 Message Date
927d6e92b0 Merge pull request 'Create Golang version devtool' (#7) from oscar-next into master
Reviewed-on: #7
2022-12-29 20:34:22 +11:00
f4f3164978 config: refactor data initialization process 2022-12-28 14:39:18 +13:00
889b7a4ca2 utils: add common function to match the directory layer 2022-12-28 14:38:14 +13:00
7f2afd1bdb subtask: add list repository task 2022-12-26 20:55:06 +13:00
d3056fc0c9 task: add exit task as the default task 2022-12-26 20:54:17 +13:00
a0e852feab subtask: allow to list volumes 2022-12-26 15:28:24 +13:00
747c774c98 task: extract the function for listing command menu 2022-12-26 01:56:02 +13:00
c32698b510 config: add comments 2022-12-26 01:10:59 +13:00
d8431550f3 task: add code security scan option 2022-12-26 01:10:16 +13:00
290e872c15 task: add curl lookup task 2022-12-26 00:53:51 +13:00
d50bf24226 task: add the task selection skeleton 2022-09-22 13:58:21 +12:00
423c0b5a37 task: support api token request 2022-09-22 13:56:37 +12:00
f25a6f7806 fix: allocate the correct size for byte array to read info from existing files 2022-09-22 09:24:10 +12:00
9c56ec0d85 utils: refactor PrintMenu with tasker array 2022-09-21 17:43:56 +12:00
a115970e7e utils: remove the config.go file 2022-09-21 17:43:21 +12:00
8463725b9c task: add tasks skeleton 2022-09-21 17:42:47 +12:00
2ab17147fb config: initialize and read config file 2022-09-21 17:00:10 +12:00
3bb03bad87 go(utils): add helper function to get config file handler 2022-09-17 21:00:56 +12:00
3f10f63c28 bash: add curl command lookup 2022-08-24 15:53:27 +12:00
f3c725b792 create a new shell with repository proned option 2022-08-24 14:01:07 +12:00
b24dcfa9d9 go(repositories): add sub menu for ee repository 2022-08-21 21:34:10 +12:00
5a6cd4ac16 go(utils): add promptMenu util function 2022-08-21 21:33:02 +12:00
4a5b954ad6 go(utils): add command util for printing output with stdout pipe 2022-08-21 21:22:41 +12:00
8028a50cc3 go(action): add the tool skeleton 2022-08-21 17:05:29 +12:00
10544e1c2d go(commands): add commands to list git branch 2022-08-21 17:04:37 +12:00
77cd6aac14 go(utils): support to print message with color 2022-08-21 17:03:53 +12:00
7b66f95832 feat(build): add yarn test into run before commit option 2022-08-16 21:40:59 +12:00
ed0284a7c7 Merge pull request 'Add ldap service quick setup' (#3) from oscar-next into master
Reviewed-on: #3
2022-08-14 15:14:52 +10:00
07c8f5d676 chore(openldap): add document 2022-08-14 16:54:52 +12:00
1ac8357335 feat(openldap): add openldap setup script 2022-08-12 19:46:33 +12:00
7c576eae4c feat(certgen/tls): allow to customize the ca certifcate name 2022-08-12 19:44:54 +12:00
75d7c6aadc feat(util): add common bash function as util 2022-08-11 22:57:16 +12:00
a65651289e feat(certgen/tls): add print color functions 2022-08-11 22:17:18 +12:00
c666bdedd1 feat(certgen/tls): allow to download cfssl bundles 2022-08-11 21:53:53 +12:00
bd80d1213f feat(certgen/tls): add the script to generate custom tls certificate in an easier way 2022-08-11 15:56:25 +12:00
56d10bc7e4 add command to retrieve golang library mod from git commit number 2022-07-15 17:56:54 +12:00
4f933b4e38 add command to generate jwt for portainer api 2022-06-23 08:02:24 +12:00
5da99536d1 update yarn package before running the actual command 2022-06-01 11:54:30 +12:00
ff95becbbd Merge pull request 'Add k8s and agent options' (#1) from oscar-next into master
Reviewed-on: #1
2022-05-19 10:39:02 +10:00
c039838a9e add option for agent build 2022-05-19 12:36:38 +12:00
7793f7e5f9 add option for k8s helm chart lint check 2022-03-24 08:42:49 +13:00
3128354700 add .gitignore 2022-03-19 16:51:57 +13:00
42 changed files with 1701 additions and 22 deletions

5
.gitignore vendored Executable file
View File

@ -0,0 +1,5 @@
/node_modules
/output
/custom_tls_cert_gen/output
yarn.lock

0
README.md Normal file → Executable file
View File

View File

@ -2,6 +2,8 @@
set -eu set -eu
source ./utils/common.sh
WORKDIR=/home/oscarzhou/source/github.com/portainer WORKDIR=/home/oscarzhou/source/github.com/portainer
GLOBAL_VOLUME=/home/oscarzhou/volumes GLOBAL_VOLUME=/home/oscarzhou/volumes
TRUE=0; TRUE=0;
@ -10,12 +12,8 @@ FALSE=1;
# PORTAINER_FLAGS= # PORTAINER_FLAGS=
# PORTAINER_FLAGS=--enable-init true # PORTAINER_FLAGS=--enable-init true
ERROR_COLOR='\033[0;31m';
HIGHLIGHT_COLOR='\033[0;32m';
NO_COLOR='\033[0m';
function choose_repo() { function choose_repo() {
read -p "Choose the working project EE/(CE):" REPO read -p "Choose the working project EE/(CE)/(k8s)/(agent):" REPO
if [ -z "$REPO" ]; then if [ -z "$REPO" ]; then
REPO="EE"; REPO="EE";
@ -26,6 +24,12 @@ function choose_repo() {
elif [[ "${REPO}" == "ce" || "${REPO}" == "CE" ]]; then elif [[ "${REPO}" == "ce" || "${REPO}" == "CE" ]]; then
cd ${WORKDIR}/portainer cd ${WORKDIR}/portainer
elif [[ "${REPO}" == "k8s" || "${REPO}" == "ks" ]]; then
cd ${WORKDIR}/k8s
elif [[ "${REPO}" == "ag" || "${REPO}" == "agent" ]]; then
cd ${WORKDIR}/agent
fi fi
} }
@ -72,11 +76,12 @@ function choose_export_volume() {
printf "${HIGHLIGHT_COLOR}export PORTAINER_DATA=${PORTAINER_DATA}${NO_COLOR}\n" printf "${HIGHLIGHT_COLOR}export PORTAINER_DATA=${PORTAINER_DATA}${NO_COLOR}\n"
} }
function prepare_temporary_volume() { function cleanup_temporary_volume() {
printf "${HIGHLIGHT_COLOR}Prepare temporary data${NO_COLOR}\n" printf "${HIGHLIGHT_COLOR}Clean temporary data${NO_COLOR}\n"
local VOLUME=~/volumes/temp-data local VOLUME=~/volumes/temp-data
if [ -d ${VOLUME} ]; then if [ -d ${VOLUME} ]; then
printf "The current volume is ${VOLUME}. "
read -p "Do you want to clean up the existing data N/(Y)?" CLEAN_DATA read -p "Do you want to clean up the existing data N/(Y)?" CLEAN_DATA
if [[ "${CLEAN_DATA}" == "y" || "${CLEAN_DATA}" == "Y" ]]; then if [[ "${CLEAN_DATA}" == "y" || "${CLEAN_DATA}" == "Y" ]]; then
rm -rvf ${VOLUME}/* rm -rvf ${VOLUME}/*
@ -89,6 +94,7 @@ function prepare_temporary_volume() {
function build_portainer_frontend_without_prompt() { function build_portainer_frontend_without_prompt() {
printf "${HIGHLIGHT_COLOR}Build Portainer Frontend${NO_COLOR}\n" printf "${HIGHLIGHT_COLOR}Build Portainer Frontend${NO_COLOR}\n"
yarn
yarn start:client yarn start:client
} }
@ -125,6 +131,16 @@ function build_portainer_backend() {
build_portainer_backend_without_prompt build_portainer_backend_without_prompt
} }
function build_portainer_agent() {
choose_repo
if ! check_branch; then
exit;
fi
./dev.sh compile
}
function build_portainer_all() { function build_portainer_all() {
printf "${HIGHLIGHT_COLOR}Build Portainer all${NO_COLOR}\n" printf "${HIGHLIGHT_COLOR}Build Portainer all${NO_COLOR}\n"
@ -150,41 +166,155 @@ function run_before_commit() {
exit; exit;
fi fi
printf "${HIGHLIGHT_COLOR}yarn format${NO_COLOR}\n" input "1. Only frontend 2. Only backend 3. Both" option
if [ option == "1" ]; then
print_highlight "yarn test";
yarn test:client
print_highlight "yarn format";
yarn format:client
print_highlight "yarn lint"
yarn lint:client
elif [ option == "2" ]; then
print_highlight "yarn test";
yarn test:server
print_highlight "yarn format";
yarn format:server
print_highlight "yarn lint"
yarn lint:server
elif [ option == "3" ]; then
print_highlight "yarn test";
yarn test
print_highlight "yarn format";
yarn format yarn format
printf "${HIGHLIGHT_COLOR}yarn lint${NO_COLOR}\n" print_highlight "yarn lint"
yarn lint yarn lint
else
print_highlight "yarn test";
yarn test:client
print_highlight "yarn format";
yarn format:client
print_highlight "yarn lint"
yarn lint:client
fi
}
function run_before_commit_k8s() {
printf "${HIGHLIGHT_COLOR}Run before commit${NO_COLOR}\n"
choose_repo
if ! check_branch; then
exit;
fi
printf "${HIGHLIGHT_COLOR}chart-testing ct lint${NO_COLOR}\n"
docker run --rm -it -w /repo -v `pwd`:/repo quay.io/helmpack/chart-testing ct lint --all --config=.ci/ct-config.yaml
}
function generate_portainer_jwt() {
printf "${HIGHLIGHT_COLOR}Generate Portainter JWT${NO_COLOR}\n"
read -p "Username(admin):" username
if [ -z "$username" ]; then
username="admin";
fi
read -p "Password(****):" password
read -p "Address(127.0.0.1):" address
if [ -z "$address" ]; then
address="127.0.0.1";
fi
payload="{\"username\":\"${username}\",\"password\":\"${password}\"}"
curl -d ${payload} -H 'Content-Type: application/json' "http://${address}:9000/api/auth"
}
function get_portainer_ce_api_reference() {
printf "${HIGHLIGHT_COLOR}Get the reference of Portainer CE API${NO_COLOR}\n"
cd ${WORKDIR}/portainer
if ! check_branch; then
exit;
fi
read -p "Commit(HEAD):" commit
if [ -z "$commit" ]; then
commit=$(git rev-parse HEAD)
fi
printf "${HIGHLIGHT_COLOR}Installing github.com/portainer/portainer/api@${commit}${NO_COLOR}\n"
output=$(go install github.com/portainer/portainer/api@${commit}) | while IFS= read -r line; do
echo "$line"
done
# result="
# go: downloading github.com/portainer/portainer/api v0.0.0-20220622202437-f0ca3e63db9d
# go: downloading github.com/portainer/portainer v0.6.1-0.20220622202437-f0ca3e63db9d
# package github.com/portainer/portainer/api is not a main package
# "
# while IFS= read -r line; do
# echo "$line"
# done <<< $(echo ${result})
} }
function menu() { function menu() {
PS3='Please select the option: ' PS3='Please select the option: '
OPTIONS=( OPTIONS=(
'Build Portainer All' 'Build Portainer EE/CE All'
'Build Portainer Frontend' 'Build Portainer EE/CE Frontend'
'Build Portainer Backend' 'Build Portainer EE/CE Backend'
'Run Before Commit' 'Generate Portainer EE/CE JWT'
'Prepare Temporary Volume' 'Run Before Commit [Portainer EE/CE]'
'Get Portainer CE API Reference'
'Run Before Commit [k8s]'
'Build Portainer Agent'
'Cleanup Temporary Volume'
'Quit' 'Quit'
) )
select opt in "${OPTIONS[@]}" select opt in "${OPTIONS[@]}"
do do
case $opt in case $opt in
'Build Portainer All') 'Build Portainer EE/CE All')
build_portainer_all build_portainer_all
;; ;;
'Build Portainer Frontend') 'Build Portainer EE/CE Frontend')
build_portainer_frontend build_portainer_frontend
;; ;;
'Build Portainer Backend') 'Build Portainer EE/CE Backend')
build_portainer_backend build_portainer_backend
;; ;;
'Run Before Commit') 'Generate Portainer EE/CE JWT')
generate_portainer_jwt
;;
'Run Before Commit [Portainer EE/CE]')
run_before_commit run_before_commit
;; ;;
'Prepare Temporary Volume') 'Get Portainer CE API Reference')
prepare_temporary_volume get_portainer_ce_api_reference
;;
'Run Before Commit [k8s]')
run_before_commit_k8s
;;
'Build Portainer Agent')
build_portainer_agent
;;
'Cleanup Temporary Volume')
cleanup_temporary_volume
;; ;;
'Quit') 'Quit')
break break

View File

@ -0,0 +1,94 @@
#!/bin/bash
set -eu
source ../utils/common.sh
input "Specify the output path:" OUTPUT_PATH
if [ -z "$OUTPUT_PATH" ]; then
OUTPUT_PATH="$(pwd)/output"
if [[ ! -e "$OUTPUT_PATH" ]]; then
mkdir "$OUTPUT_PATH"
fi
fi
if [[ ! -e "$OUTPUT_PATH" ]]; then
print_error "${OUTPUT_PATH} doesn't exist."
exit;
fi
rm -rvf "$OUTPUT_PATH/*"
input "Do you have cfssl installed?(y/n): " is_cfssl_installed
CFSSLEXE=${OUTPUT_PATH}/cfssl
CFSSLJSONEXE=${OUTPUT_PATH}/cfssljson
if [[ "${is_cfssl_installed}" == "y" || "${is_cfssl_installed}" == "Y" ]]; then
input "Specify the path where the cfssl and cfssljson are placed: " TOOL_PATH
CFSSLEXE=${TOOL_PATH}/cfssl
CFSSLJSONEXE=${TOOL_PATH}/cfssljson
print_highlight "Your cfssl binary path is ${CFSSLEXE}"
if [ ! -e "$CFSSLEXE" ]; then
print_error "no cfssl found."
exit;
fi
if [ ! -e "$CFSSLJSONEXE" ]; then
print_error "no cfssljson found."
exit;
fi
else
# Download the cfssl for users
input "Specify your platform(darwin/linux/windows): " PLATFORM
if [ -z "$PLATFORM" ]; then
print_error "Platform must be provided."
exit;
fi
print_highlight "Only amd64 is supported"
wget "https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssl_1.6.1_${PLATFORM}_amd64" -O "${OUTPUT_PATH}/cfssl"
chmod +x "${OUTPUT_PATH}/cfssl"
wget "https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssljson_1.6.1_${PLATFORM}_amd64" -O "${OUTPUT_PATH}/cfssljson"
chmod +x "${OUTPUT_PATH}/cfssljson"
print_highlight "Download the cfssl bundle successfully."
fi
cd $OUTPUT_PATH
input "Give a name to the CA certificate: " CA_CERT_NAME
CA_CERT_NAME=${CA_CERT_NAME}-ca
${CFSSLEXE} print-defaults csr | ${CFSSLEXE} gencert -initca - | ${CFSSLJSONEXE} -bare ${CA_CERT_NAME}
CONFIG_CFSSL_JSON=${OUTPUT_PATH}/cfssl.json
cat <<EOF >> ${CONFIG_CFSSL_JSON}
{
"signing": {
"default": {
"expiry": "87600h",
"usages": ["signing", "key encipherment", "server auth"]
}
}
}
EOF
input "Give a name to the certificate: " CERT_NAME
input "Input the hostname(example.org,127.0.0.1): " CERT_HOSTNAME
echo '{}' | ${CFSSLEXE} gencert -ca=${CA_CERT_NAME}.pem -ca-key=${CA_CERT_NAME}-key.pem -config=${CONFIG_CFSSL_JSON} \
-hostname="${CERT_HOSTNAME}" - | ${CFSSLJSONEXE} -bare ${CERT_NAME}
print_highlight "The custom TLS certificates are successfully generated in the path ${OUTPUT_PATH}."

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
}

BIN
images/setup-openldap.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

32
ldap_service/README.md Normal file
View File

@ -0,0 +1,32 @@
# LDAP
This will setup portainer with testing image and openldap service with bootstrap data + StartTLS/TLS enabled
## 1. How to start?
```
git clone https://github.com/oscarzhou/portainer-openldap-quick-setup.git && cd portainer-openldap-quick-setup
chmod +x ldap-run.sh
./ldap-run.sh
```
![setup-openldap](/images/setup-openldap.gif)
After the output `Portainer run up successfully` shows up, it may take a while for portainer to finish initialization. You can refresh the web page every 5 seconds.
## 2. How to test?
| Key | Value |
|---|---|
| Admin Login DN | cn=admin,dc=example,dc=org |
| Admin Password | admin_pass |
| Server IP | 172.31.0.10 |
| Port over TLS (STARTTLS) | 389 |
| Port over SSL | 636 |
| CA Certificate | ./data/certs/ldap-ca.pem |
| username1 | developer |
| password1 | developer_pass |
| username2 | maintainer |
| password2 | maintainer_pass |

View File

@ -0,0 +1,54 @@
dn: cn=developer,dc=example,dc=org
changetype: add
objectclass: inetOrgPerson
cn: developer
givenname: developer
sn: Developer
displayname: Developer User
mail: developer@gmail.com
uid: developer
userpassword: developer_pass
dn: cn=maintainer,dc=example,dc=org
changetype: add
objectclass: inetOrgPerson
cn: maintainer
givenname: maintainer
sn: Maintainer
displayname: Maintainer User
mail: maintainer@gmail.com
uid: maintainer
userpassword: maintainer_pass
dn: cn=admin_gh,dc=example,dc=org
changetype: add
objectclass: inetOrgPerson
cn: admin_gh
givenname: admin_gh
sn: AdminGithub
displayname: Admin Github User
mail: admin_gh@gmail.com
userpassword: admin_gh_pass
dn: ou=Groups,dc=example,dc=org
changetype: add
objectclass: organizationalUnit
ou: Groups
dn: ou=Users,dc=example,dc=org
changetype: add
objectclass: organizationalUnit
ou: Users
dn: cn=Admins,ou=Groups,dc=example,dc=org
changetype: add
cn: Admins
objectclass: groupOfUniqueNames
uniqueMember: cn=admin_gh,dc=example,dc=org
dn: cn=Maintainers,ou=Groups,dc=example,dc=org
changetype: add
cn: Maintainers
objectclass: groupOfUniqueNames
uniqueMember: cn=maintainer,dc=example,dc=org
uniqueMember: cn=developer,dc=example,dc=org

View File

@ -0,0 +1,8 @@
{
"signing": {
"default": {
"expiry": "87600h",
"usages": ["signing", "key encipherment", "server auth"]
}
}
}

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOnBF74dtgfFSwUZlY3WPHjLyedZ3YI5H5jrEu33FeX0oAoGCCqGSM49
AwEHoUQDQgAEc7tkckI5XrRSf+QeUhk4xnSJdabwgpPVY9vg+DdpFocK7i99ubI+
p5rBX9xrKGKlcEmM/Yufh32b1drdHmQFaQ==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBPTCB5AIBADBIMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcT
DVNhbiBGcmFuY2lzY28xFDASBgNVBAMTC2V4YW1wbGUubmV0MFkwEwYHKoZIzj0C
AQYIKoZIzj0DAQcDQgAEc7tkckI5XrRSf+QeUhk4xnSJdabwgpPVY9vg+DdpFocK
7i99ubI+p5rBX9xrKGKlcEmM/Yufh32b1drdHmQFaaA6MDgGCSqGSIb3DQEJDjEr
MCkwJwYDVR0RBCAwHoILZXhhbXBsZS5uZXSCD3d3dy5leGFtcGxlLm5ldDAKBggq
hkjOPQQDAgNIADBFAiEA8F+6ILOqzCzCuPB+sgUALDeud27CEu9nIM16cG710ioC
IBPdKdWivdCVG+YO/+mYb/g3Hbk5vByB9xj1bVQtt7KE
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB0zCCAXqgAwIBAgIUGzxUvE3E82RNYhT+eG2Hscq7ma4wCgYIKoZIzj0EAwIw
SDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
c2NvMRQwEgYDVQQDEwtleGFtcGxlLm5ldDAeFw0yMjA4MTEyMjM4MDBaFw0yNzA4
MTAyMjM4MDBaMEgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMN
U2FuIEZyYW5jaXNjbzEUMBIGA1UEAxMLZXhhbXBsZS5uZXQwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAARzu2RyQjletFJ/5B5SGTjGdIl1pvCCk9Vj2+D4N2kWhwru
L325sj6nmsFf3GsoYqVwSYz9i5+HfZvV2t0eZAVpo0IwQDAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBglz2vX3F2/QSfPb8CE6WgX
xowwCgYIKoZIzj0EAwIDRwAwRAIgTPJwMJ/C1AWyduH1VHateYtwSsSiG4CFof/m
e7Te0SACIDo8NbjqCxX5q7xNREx/KrWAGblLlk00Ywsqc+qZejC0
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIxGYodBnPD2v4PlKVfTZYkPl2kf9ckdT63NRVI8pJt8oAoGCCqGSM49
AwEHoUQDQgAEj0t/ND963wlU/FFeiwI7cSBqkOX4puNwOz/npMwVwYLuVOrY/L+s
hjPrZ32WW3lAu3NKsG7bkbDzzw76ppbY1w==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,8 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBADCBpwIBADAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEj0t/ND963wlU
/FFeiwI7cSBqkOX4puNwOz/npMwVwYLuVOrY/L+shjPrZ32WW3lAu3NKsG7bkbDz
zw76ppbY16BFMEMGCSqGSIb3DQEJDjE2MDQwMgYDVR0RBCswKYIQbGRhcC5leGFt
cGxlLm9yZ4IJbG9jYWxob3N0hwSsHwAKhwR/AAABMAoGCCqGSM49BAMCA0gAMEUC
IQCMPYIchbVmp1bmvT77sucgUf4fe7CGSdOVWkL3rxkTjAIgIhHivuP62hOyG43O
xWi/83L01C7MiOOsQMu6x3NEYQI=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB+DCCAZ6gAwIBAgIUTj+0B76Ev+XH/iW7lPdo28KM884wCgYIKoZIzj0EAwIw
SDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
c2NvMRQwEgYDVQQDEwtleGFtcGxlLm5ldDAeFw0yMjA4MTEyMjM4MDBaFw0zMjA4
MDgyMjM4MDBaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASPS380P3rfCVT8
UV6LAjtxIGqQ5fim43A7P+ekzBXBgu5U6tj8v6yGM+tnfZZbeUC7c0qwbtuRsPPP
DvqmltjXo4GtMIGqMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcD
ATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSefdqGwGdpVarg54WkJioV315BOzAf
BgNVHSMEGDAWgBQ4GCXPa9fcXb9BJ89vwITpaBfGjDA1BgNVHREBAf8EKzApghBs
ZGFwLmV4YW1wbGUub3Jngglsb2NhbGhvc3SHBKwfAAqHBH8AAAEwCgYIKoZIzj0E
AwIDSAAwRQIgIyv9Rifo3PThZm43YJ2nEIeOVANoUHaS1eD34YfLO64CIQDOvpMk
WtM/tAn7ufxdRcN51ev6maK6yQMiu4Hj6Fk4gg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,47 @@
version: '3.7'
services:
ldap_server:
image: osixia/openldap:1.5.0
container_name: ldap_server
environment:
LDAP_ADMIN_PASSWORD: admin_pass
LDAP_BASE_DN: dc=example,dc=org
LDAP_DOMAIN: example.org
LDAP_ORGANISATION: "Example Inc."
LDAP_TLS_CRT_FILENAME: server.pem
LDAP_TLS_KEY_FILENAME: server-key.pem
LDAP_TLS_CA_CRT_FILENAME: ldap-ca.pem
LDAP_TLS_VERIFY_CLIENT: try
hostname: ldap.example.org
command: --copy-service
networks:
default:
ipv4_address: 172.31.0.10
ports:
- 389:389
- 636:636
volumes:
- ./data/bootstrap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/50-bootstrap.ldif
- ./data/certs:/container/service/slapd/assets/certs
ldap_server_admin:
image: osixia/phpldapadmin:0.7.2
container_name: ldap_server_admin
ports:
- 8090:80
networks:
default:
ipv4_address: 172.31.0.2
environment:
PHPLDAPADMIN_LDAP_HOSTS: ldap_server
PHPLDAPADMIN_HTTPS: 'false'
networks:
default:
external: false
name: openldap-network
ipam:
driver: default
config:
- subnet: "172.31.0.1/16"

117
ldap_service/ldap-run.sh Executable file
View File

@ -0,0 +1,117 @@
#!/bin/bash
set -eu
source ../utils/common.sh
DOCKER_COMPOSE_FILE=./docker-compose.yml
BOOTSTRAP_FILE=./data/bootstrap.ldif
CA_CERT_FILE=./data/certs/ldap-ca.pem
CERT_FILE=./data/certs/server.pem
KEY_FILE=./data/certs/server-key.pem
print_highlight "Start setup ldap service..."
docker-compose -v | grep 'docker-compose version' &> /dev/null
if [ $? != 0 ]; then
print_error "docker-compose not detected"
exit;
fi
print_highlight "docker-compose detected" &> /dev/null
set +e
docker container ls -a | grep 'portainer_ldap' &> /dev/null
if [ $? == 0 ]; then
docker stop portainer_ldap
docker rm portainer_ldap
print_highlight "removing existing container portainer_ldap"
fi
docker volume ls | grep 'portainer_ldap_data'
if [ $? == 0 ]; then
docker volume rm portainer_ldap_data
print_highlight "removing existing volume portainer_ldap_data"
fi
docker container ls -a | grep 'ldap_server' &> /dev/null
if [ $? == 0 ]; then
docker stop ldap_server
docker rm ldap_server
print_highlight "removing existing container ldap_server"
fi
docker container ls -a | grep 'ldap_server_admin' &> /dev/null
if [ $? == 0 ]; then
docker stop ldap_server_admin
docker rm ldap_server_admin
print_highlight "removing existing container ldap_server_admin"
fi
docker network ls | grep 'openldap-network' &> /dev/null
if [ $? == 0 ]; then
docker network rm openldap-network
print_highlight "removing existing container openldap-network"
fi
set -e
if [[ ! -e "${DOCKER_COMPOSE_FILE}" ]]; then
print_error "${DOCKER_COMPOSE_FILE} not found"
exit;
fi
if [[ ! -e "${BOOTSTRAP_FILE}" ]]; then
print_error "${BOOTSTRAP_FILE} not found"
exit;
fi
if [[ ! -e "${CA_CERT_FILE}" ]]; then
print_error "${CA_CERT_FILE } not found"
exit;
fi
if [[ ! -e "${CERT_FILE}" ]]; then
print_error "${CERT_FILE} not found"
exit;
fi
if [[ ! -e "${KEY_FILE}" ]]; then
print_error "${KEY_FILE } not found"
exit;
fi
docker-compose up -d
print_highlight "Open LDAP service run up successfully."
print_highlight "Login DN(username): cn=admin,dc=example,dc=org"
print_highlight "Password: admin_pass"
sleep 5
xdg-open http://localhost:8090
sleep 5
input "Input your testing docker image(portainerci/portainer-ee:prxxx): " TEST_IMAGE
docker volume create portainer_ldap_data
docker run -d \
-p 8000:8000 \
-p 9000:9000 \
-p 9443:9443 \
--network openldap-network \
--name portainer_ldap \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /portainer_ldap_data:/data \
${TEST_IMAGE}
print_highlight "Portainer run up successfully."
sleep 10
xdg-open http://localhost:9000

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

31
utils/common.sh Normal file
View File

@ -0,0 +1,31 @@
#!/bin/bash
TRUE=0;
FALSE=1;
ERROR_COLOR='\033[0;31m';
HIGHLIGHT_COLOR='\033[0;32m';
INPUT_COLOR='\033[0;33m';
NO_COLOR='\033[0m';
function print_highlight() {
printf "${HIGHLIGHT_COLOR}$1${NO_COLOR}\n"
}
function print_error() {
printf "${ERROR_COLOR}$1${NO_COLOR}\n"
}
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;
}