commit fd4731129a54fb55764f286e42bdb64c5fecfc4f Author: Ваше Имя Date: Sun Mar 2 18:51:02 2025 +0500 first commit diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..dd98a0c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}" + }, + { + "name": "Attach to Running Go Program", + "type": "go", + "request": "attach", + "mode": "remote", + "remotePath": "${workspaceFolder}", + "port": 40000, + "host": "127.0.0.1" + } + ] +} \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..2312cb6 --- /dev/null +++ b/Readme.md @@ -0,0 +1,2 @@ +# Debug +```dlv debug --headless --listen=:40000 --api-version=2``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..30145c6 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module hello + +go 1.23.5 + +require ( + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..33063f7 --- /dev/null +++ b/go.sum @@ -0,0 +1,5 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/hello b/hello new file mode 100755 index 0000000..212ca99 Binary files /dev/null and b/hello differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..1185c3e --- /dev/null +++ b/main.go @@ -0,0 +1,342 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + + "gopkg.in/ini.v1" + "gopkg.in/yaml.v2" +) + +// Variables описывает работу с .ini файлами +type Variables struct { + iniFileName string + config *ini.File +} + +// NewVariables конструктор для работы с .ini файлами +func NewVariables(iniFileName string) *Variables { + config, err := ini.LooseLoad(iniFileName) + if err != nil { + log.Fatalf("Ошибка загрузки ini-файла: %v", err) + } + + return &Variables{ + iniFileName: iniFileName, + config: config, + } +} + +// GetVar читает или запрашивает переменную из .ini +func (v *Variables) GetVar(sectionName, varName string, interactive bool) string { + section := v.config.Section(sectionName) + value := section.Key(varName).String() + + if interactive || value == "" { + reader := bufio.NewReader(os.Stdin) + fmt.Printf("Введите %s (%s): ", varName, value) + newValue, _ := reader.ReadString('\n') + newValue = strings.TrimSpace(newValue) + + if newValue != "" { + section.Key(varName).SetValue(newValue) + v.config.SaveTo(v.iniFileName) + return newValue + } + } + if !interactive && value == "" { + fmt.Printf("Переменная %s не найдена", varName) + os.Exit(1) + } + + return value +} + +// MenuItem описывает структуру меню из YAML +type MenuItem struct { + Title string `yaml:"title"` + File string `yaml:"file,omitempty"` + Items map[int]MenuItem `yaml:"items,omitempty"` + Commands []string `yaml:"commands,omitempty"` + Catch []string `yaml:"catch,omitempty"` + Vars []string `yaml:"vars,omitempty"` + Env string `yaml:"env,omitempty"` +} + +// Menu управляет отображением меню +type Menu struct { + items map[int]MenuItem + variables *Variables + baseDir string +} + +// NewMenu инициализирует меню из YAML +func NewMenu(yamlFile string) *Menu { + currDir, _ := os.Getwd() + // baseDir := filepath.Join(currDir, "menu") // Базовая директория меню + baseDir := currDir + yamlPath := resolvePath(baseDir, yamlFile) + + // Читаем YAML файл + file, err := os.Open(yamlPath) + if err != nil { + log.Fatalf("Ошибка чтения YAML файла %s: %v", yamlPath, err) + } + defer file.Close() + + decoder := yaml.NewDecoder(file) + var menu struct { + Items map[int]MenuItem `yaml:"items"` + } + + if err := decoder.Decode(&menu); err != nil { + log.Fatalf("Ошибка парсинга YAML: %v", err) + } + + return &Menu{ + items: menu.Items, + variables: NewVariables("env.ini"), + baseDir: baseDir, + } +} + +// resolvePath корректно формирует путь к файлу +func resolvePath(baseDir, yamlFile string) string { + // Если путь абсолютный, используем его как есть + if filepath.IsAbs(yamlFile) { + return yamlFile + } + + // Если путь уже содержит сегмент "menu", оставляем всё как есть + if strings.HasPrefix(baseDir, "menu"+string(os.PathSeparator)) { + return filepath.Join(baseDir, yamlFile) + } + + // Обычно добавляем сегмент "menu" перед файлом + return filepath.Join(baseDir, string(os.PathSeparator)+"menu", yamlFile) +} + +// ShowItems отображает главное меню +func (m *Menu) ShowItems() { + fmt.Println("0. Выход") + + //Извлекаем ключи из мапы + keys := make([]int, 0, len(m.items)) + for key := range m.items { + keys = append(keys, key) + } + + //Сортируем ключи + sort.Ints(keys) + + for _, key := range keys { + fmt.Printf("%s\n", m.items[key].Title) + } +} + +// Loop запускает интерактивный цикл меню +func (m *Menu) Loop() { + reader := bufio.NewReader(os.Stdin) + + for { + m.ShowItems() + fmt.Print("Выберите действие: ") + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + + if input == "0" { + break + } + + if itemNum, err := strconv.Atoi(input); err == nil { + m.ChooseItem(itemNum) + } else { + fmt.Println("Неверный ввод") + } + } +} + +// ChooseItem обрабатывает выбор элемента меню +func (m *Menu) ChooseItem(itemNum int) { + item, found := m.items[itemNum] + if !found { + fmt.Println("Элемент не найден") + return + } + + if len(item.File) > 0 { + // Подгружаем дочернее меню + f := strings.TrimPrefix(item.File, string(os.PathSeparator)) + filePath := resolvePath(m.baseDir, f) + subMenu := NewMenu(filePath) + subMenu.Loop() + } else if len(item.Items) > 0 { + subMenu := &Menu{items: item.Items, variables: m.variables, baseDir: m.baseDir} + subMenu.Loop() + } else if len(item.Commands) > 0 { + vars := m.getVars(item, true) + if !m.runCommands(item, vars) { + m.catch(item, vars) + } + } +} + +func (m *Menu) ExecItem(itemNum int) *Menu { + item, found := m.items[itemNum] + if !found { + fmt.Println("Элемент не найден") + return nil + } + + if len(item.File) > 0 { + // Подгружаем дочернее меню + f := strings.TrimPrefix(item.File, string(os.PathSeparator)) + filePath := resolvePath(m.baseDir, f) + subMenu := NewMenu(filePath) + return subMenu + } else if len(item.Items) > 0 { + subMenu := &Menu{items: item.Items, variables: m.variables, baseDir: m.baseDir} + return subMenu + } else if len(item.Commands) > 0 { + vars := m.getVars(item, false) + if !m.runCommands(item, vars) { + m.catch(item, vars) + } + } + return nil +} + +// getVars получает переменные из .ini +func (m *Menu) getVars(item MenuItem, interactive bool) map[string]string { + vars := make(map[string]string) + vars["currentDir"], _ = os.Getwd() + + for _, varName := range item.Vars { + vars[varName] = m.variables.GetVar(item.Env, varName, interactive) + } + + return vars +} + +// runCommands выполняет команды +func (m *Menu) runCommands(item MenuItem, vars map[string]string) bool { + for _, cmd := range item.Commands { + // Здесь можно вызвать реальное выполнение, используя os/exec + re := regexp.MustCompile(`%\(([a-zA-Z0-9]*)\)s`) + command := re.ReplaceAllStringFunc(cmd, func(match string) string { + // Извлекаем ключ внутри %( и )s + key := re.FindStringSubmatch(match)[1] + + // Проверяем, есть ли значение для ключа в map + if val, exists := vars[key]; exists { + return val // Заменяем найденной строкой + } + // Если ключа нет в map, оставляем как есть + return match + }) + fmt.Printf("Выполнение: %s\n", command) + + // Разделяем на части + parts := strings.Split(command, " ") + + // Проверяем, является ли первая часть установкой переменной окружения + envs := []string{} // Хранилище для переменных окружения + for len(parts) > 0 && strings.Contains(parts[0], "=") { + envs = append(envs, parts[0]) // Добавляем переменную в массив env + parts = parts[1:] // Убираем первую часть + } + + // Теперь `parts` содержит только саму команду и её аргументы + cmd := exec.Command(parts[0], parts[1:]...) + + // Добавляем переменные окружения к текущим + cmd.Env = append(os.Environ(), envs...) + // Передаём текущее окружение + // cmd.Env = os.Environ() + cmd.Stdin = os.Stdin // Ввод + cmd.Stdout = os.Stdout // Вывод + cmd.Stderr = os.Stderr // Вывод ошибок + + // Запускаем команду + if err := cmd.Run(); err != nil { + os.Stderr.WriteString("Ошибка выполнения: " + err.Error() + "\n") + } + } + return true +} + +// catch выполняет обработку ошибок команд +func (m *Menu) catch(item MenuItem, vars map[string]string) { + for _, cmd := range item.Catch { + fmt.Printf("Обработка ошибки: %s\n", cmd) + // Здесь можно вызвать реальное выполнение, используя os/exec + re := regexp.MustCompile(`%\(([a-zA-Z0-9]*)\)s`) + command := re.ReplaceAllStringFunc(cmd, func(match string) string { + // Извлекаем ключ внутри %( и )s + key := re.FindStringSubmatch(match)[1] + + // Проверяем, есть ли значение для ключа в map + if val, exists := vars[key]; exists { + return val // Заменяем найденной строкой + } + // Если ключа нет в map, оставляем как есть + return match + }) + fmt.Printf("Выполнение: %s\n", command) + + // Разделяем на части + parts := strings.Split(command, " ") + + // Проверяем, является ли первая часть установкой переменной окружения + envs := []string{} // Хранилище для переменных окружения + for len(parts) > 0 && strings.Contains(parts[0], "=") { + envs = append(envs, parts[0]) // Добавляем переменную в массив env + parts = parts[1:] // Убираем первую часть + } + + // Теперь `parts` содержит только саму команду и её аргументы + cmd := exec.Command(parts[0], parts[1:]...) + + // Добавляем переменные окружения к текущим + cmd.Env = append(os.Environ(), envs...) + // Передаём текущее окружение + // cmd.Env = os.Environ() + cmd.Stdin = os.Stdin // Ввод + cmd.Stdout = os.Stdout // Вывод + cmd.Stderr = os.Stderr // Вывод ошибок + + // Запускаем команду + if err := cmd.Run(); err != nil { + os.Stderr.WriteString("Ошибка выполнения: " + err.Error() + "\n") + } + } +} + +func main() { + // Инициализация главного меню + menu := NewMenu("menu.yml") // Файл главного меню (menu/menu.yml) + + // Создаем флаг -e/--exec с необязательным аргументом + execFlag := flag.String("exec", "", "Execution parameter") + flag.Parse() // Парсим аргументы + + if *execFlag != "" { // Проверяем, задан ли флаг --exec + items := strings.Split(*execFlag, "-") // Разделяем строку по "-" + for _, item := range items { // Итерируемся по элементам + num, _ := strconv.Atoi(item) + menu = menu.ExecItem(num) + } + os.Exit(0) // Завершаем выполнение программы + } + + menu.Loop() +} diff --git a/menu/develop/menu.yml b/menu/develop/menu.yml new file mode 100755 index 0000000..620754a --- /dev/null +++ b/menu/develop/menu.yml @@ -0,0 +1,108 @@ +items: + 1: + title: '1. Build' + env: 'dev' + commands: + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_dev.yml build --no-cache' + vars: + - appName + 2: + title: '2. InitDB' + env: 'dev' + commands: + - 'docker run -v %(currentDir)s/data/mysql:/var/lib/mysql -v %(currentDir)s/dumps:/home/dumps -v %(currentDir)s/conf/scripts/mysql:/home/scripts -it %(appName)s_db sh /home/scripts/initDB.sh' + vars: + - appName + 3: + title: '3. Deploy and Up' + env: 'dev' + commands: + - 'mkdir -p data/mysql' + - 'mkdir -p app' + - 'docker run -v %(currentDir)s/data/mysql:/var/lib/mysql -v %(currentDir)s/dumps:/home/dumps -v %(currentDir)s/conf/scripts/mysql:/home/scripts -it %(appName)s_db sh /home/scripts/initDB.sh' + - 'sh conf/scripts/deploy/dev.sh' + - 'docker network create -d bridge --subnet 192.168.0.0/24 --gateway 192.168.0.1 dockernet' + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_dev.yml up --force-recreate -d' + - 'docker exec -it %(appName)s_workspace_1 sh /home/scripts/deploy_dev.sh' + vars: + - appName + 4: + title: '4. Up' + env: 'dev' + commands: + - 'docker network create -d bridge --subnet 192.168.0.0/24 --gateway 192.168.0.1 dockernet' + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_dev.yml up --force-recreate -d' + vars: + - appName + 5: + title: '5. Stop' + env: 'dev' + commands: + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_dev.yml stop' + - 'sleep 3' + - 'docker network rm dockernet' + vars: + - appName + 6: + title: '6. Remove' + env: 'dev' + commands: + - 'docker rm %(appName)s_php_1 --force' + - 'docker rm %(appName)s_workspace_1 --force' + - 'docker rm %(appName)s_db_1 --force' + - 'docker rm %(appName)s_nginx_1 --force' + - 'docker rm %(appName)s_nodejs_1 --force' + - 'docker rm %(appName)s_python_1 --force' + - 'docker rm %(appName)s_ftp_1 --force' + - 'docker rm %(appName)s_elastic_1 --force' + - 'docker rmi %(appName)s_php --force' + - 'docker rmi %(appName)s_workspace --force' + - 'docker rmi %(appName)s_db --force' + - 'docker rmi %(appName)s_nginx --force' + - 'docker rmi %(appName)s_nodejs --force' + - 'docker rmi %(appName)s_python --force' + - 'docker rmi %(appName)s_ftp --force' + - 'docker rmi %(appName)s_elastic --force' + vars: + - appName + 7: + title: '7. In php workspace' + env: 'dev' + commands: + - 'docker exec -it %(appName)s_workspace_1 sh' + vars: + - appName + 8: + title: '8. In nodejs workspace' + env: 'dev' + commands: + - 'docker exec -it %(appName)s_nodejs_1 /bin/bash' + vars: + - appName + 9: + title: '9. Make Dump' + env: 'dev' + commands: + - 'docker exec -it %(appName)s_db_1 sh /home/scripts/makeDump.sh dump.sql' + vars: + - appName + 10: + title: '10. SSHFS' + items: + 1: + title: '1. Mount' + env: 'dev' + commands: + - 'sshfs %(user)s@%(ip)s:%(remotePath)s %(currentDir)s/server' + vars: + - appName + - user + - ip + - remotePath + 2: + title: '2. Umount' + env: 'dev' + commands: + - 'sudo umount -l %(currentDir)s/server' + vars: + - appName \ No newline at end of file diff --git a/menu/menu.yml b/menu/menu.yml new file mode 100755 index 0000000..a457718 --- /dev/null +++ b/menu/menu.yml @@ -0,0 +1,10 @@ +items: + 1: + title: '1. Develop' + file: '/develop/menu.yml' + 2: + title: '2. Test' + file: '/test/menu.yml' + 3: + title: '3. Production' + file: '/prod/menu.yml' \ No newline at end of file diff --git a/menu/prod/menu.yml b/menu/prod/menu.yml new file mode 100755 index 0000000..9f3f629 --- /dev/null +++ b/menu/prod/menu.yml @@ -0,0 +1,59 @@ +items: + 1: + title: '1. Build' + env: 'test' + commands: + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_prod.yml build --no-cache' + vars: + - appName + 2: + title: '2. InitDB' + env: 'test' + commands: + - 'docker run -v %(currentDir)s/data/mysql:/var/lib/mysql -v %(currentDir)s/dumps:/home/dumps -v %(currentDir)s/conf/scripts/mysql:/home/scripts -it %(appName)s_db sh /home/scripts/initDB.sh' + vars: + - appName + 3: + title: '3. Deploy and Up' + env: 'test' + commands: + - 'mkdir -p data/mysql' + - 'mkdir -p app' + - 'sh conf/scripts/deploy/test.sh' + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_prod.yml up --force-recreate -d' + vars: + - appName + 4: + title: '4. Up' + env: 'test' + commands: + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_prod.yml up --force-recreate -d' + vars: + - appName + 5: + title: '5. Stop' + env: 'test' + commands: + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_prod.yml stop' + - 'sleep 3' + - 'docker network rm dockernet' + vars: + - appName + 6: + title: '6. Remove' + env: 'test' + commands: + - 'docker rm %(appName)s_php_1 --force' + - 'docker rm %(appName)s_db_1 --force' + - 'docker rm %(appName)s_nginx_1 --force' + - 'docker rm %(appName)s_ftp_1 --force' + - 'docker rm %(appName)s_elastic_1 --force' + - 'docker rm %(appName)s_redis_1 --force' + - 'docker rmi %(appName)s_php --force' + - 'docker rmi %(appName)s_db --force' + - 'docker rmi %(appName)s_nginx --force' + - 'docker rm %(appName)s_ftp --force' + - 'docker rm %(appName)s_elastic --force' + - 'docker rm %(appName)s_redis --force' + vars: + - appName \ No newline at end of file diff --git a/menu/test/menu.yml b/menu/test/menu.yml new file mode 100755 index 0000000..085eb3e --- /dev/null +++ b/menu/test/menu.yml @@ -0,0 +1,59 @@ +items: + 1: + title: '1. Build' + env: 'test' + commands: + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_test.yml build --no-cache' + vars: + - appName + 2: + title: '2. InitDB' + env: 'test' + commands: + - 'docker run -v %(currentDir)s/data/mysql:/var/lib/mysql -v %(currentDir)s/dumps:/home/dumps -v %(currentDir)s/conf/scripts/mysql:/home/scripts -it %(appName)s_db sh /home/scripts/initDB.sh' + vars: + - appName + 3: + title: '3. Deploy and Up' + env: 'test' + commands: + - 'mkdir -p data/mysql' + - 'mkdir -p app' + - 'sh conf/scripts/deploy/test.sh' + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_test.yml up --force-recreate -d' + vars: + - appName + 4: + title: '4. Up' + env: 'test' + commands: + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_test.yml up --force-recreate -d' + vars: + - appName + 5: + title: '5. Stop' + env: 'test' + commands: + - 'docker-compose --project-name %(appName)s --file conf/docker/docker-compose_test.yml stop' + - 'sleep 3' + - 'docker network rm dockernet' + vars: + - appName + 6: + title: '6. Remove' + env: 'test' + commands: + - 'docker rm %(appName)s_php_1 --force' + - 'docker rm %(appName)s_db_1 --force' + - 'docker rm %(appName)s_nginx_1 --force' + - 'docker rm %(appName)s_ftp_1 --force' + - 'docker rm %(appName)s_elastic_1 --force' + - 'docker rm %(appName)s_redis_1 --force' + - 'docker rmi %(appName)s_php --force' + - 'docker rmi %(appName)s_db --force' + - 'docker rmi %(appName)s_nginx --force' + - 'docker rm %(appName)s_ftp --force' + - 'docker rm %(appName)s_elastic --force' + - 'docker rm %(appName)s_redis --force' + vars: + - appName \ No newline at end of file