Go для системных администраторов. Практические примеры. Часть 0

Здравствуйте, меня зовут Виталий и я обезьяна практик — для меня лучше один раз увидеть и скопировать, чем сто раз прочитать абстрактные руководства. Долгое время я был обычным системным администратором — писал скрипты на CMD/BAT, и даже на sh (при помощи busybox для Windows). Но однажды обычного shell мне стало не хватать, и я решил для себя написать собственный RPC-сервер, но так, чтобы он работал при минимуме системных компонентов, и был понятным, и был многопоточным и содержал минимум строк кода. Java и прочее ООП я отмел, так как для профессионалов, и слишком абстрактно, и надо ставить среду для выполнения на целевой компьютер, и мне же, как админу, её обновлять. Долгое время приглядывался к perl, но я боюсь динамической типизации. В статье я расскажу, как человеку мало знакомому с программированием решить некоторые задачи системного администрирования при помощи Go.

Я предполагаю, что вы осилили «Быстрый старт – программируем на Go под Windows — настройка Environment», имеете опыт написания простейших скриптов. А еще я соврал. На целевой машине может потребоваться Microsoft Visual C++.

Для начала мы попробуем превратить простой скрипт в приложение на Go. Для примера возьмем test.bat:

@echo off
set URL1="ftp://user:pass@88.88.88.88/test.zip"
set URL2="ftp://user1:pass1@99.99.99.99/exchange/test.zip"
set SAVE_PATH=".\test\test.zip"
echo Первый источник
curl %URL1% -o %SAVE_PATH%
if %errorlevel% NEQ 0 (
echo  Загрузка из первого источника закончилась с ошибкой: %errorlevel%
curl %URL2% -o %SAVE_PATH%
)

Минутка любви к Microsoft. Если я хочу проверить, скачался ли файл из второго источника, то я должен использовать GOTO, т. к. внутри IF и FOR %errorlevel% и %time% остаются такими же как перед вызовом IF и FOR.

Примерно так будет выглядеть наш скрипт на Go:

package main

import (
	"fmt"
	"os"
	"os/exec"
)

func main() {
	url1 := "ftp://user:pass@88.88.88.88/test.zip"
	url2 := "ftp://user1:pass1@99.99.99.99/exchange/test.zip"
	out_file := ".\\test\\test.zip"     //В Go бэкслэш используется для экранирования, так что в пути windows его придется удваивать.
	err := start_curl(url1, out_file)
	if err != nil {
		fmt.Printf("Загрузка из первого источника закончилась с ошибкой: %v\r\n", err)
		err = start_curl(url2, out_file)
		if err != nil {
			fmt.Printf("Загрузка из второго источника закончилась с ошибкой: %v\r\n", err)
			os.Exit(1)
		}
	}
	fmt.Printf("Загрузка успешно завершена\r\n")
}

func start_curl(url string, out_file string) error {
	cmd := exec.Command("curl", url, "-o", out_file)
	err := cmd.Run()
	return err
}

Запустить мы запустили, но для отладки неплохо было бы увидеть, что выдает curl в stderr/stdout. Кстати, curl все выдает в stderr:

func start_curl(url string, out_file string) error {
	cmd := exec.Command("curl", url, "-o", out_file)
	//cmd.Stdout = os.Stdout     //Закомментировать строку можно можно двумя слэшами.
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	return err
}

При ошибке функция start_curl() возвращает что-то вроде «exit code 7». А нам бы хотелось получить код возврата в виде числа. Мы можем отрезать строку «exit_code » и и преобразовать строку «7» в число7. Для этого придется импортировать пакеты «strings» и «strconv». Но есть более простой, и менее понятный способ:

package main

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
)

func main() {
	url1 := "ftp://user:pass@88.88.88.88/test.zip"
	url2 := "ftp://user1:pass1@99.99.99.99/exchange/test.zip"
	out_file := ".\\test\\test.zip"
	int_err := start_curl(url1, out_file)
	if int_err != 0 {
		fmt.Printf("Загрузка из первого источника закончилась с ошибкой: %d\r\n", int_err)
		int_err = start_curl(url2, out_file)
		if int_err != 0 {
			fmt.Printf("Загрузка из второго источника закончилась с ошибкой: %d\r\n", int_err)
			os.Exit(int_err)
		}
	}
	fmt.Printf("Загрузка успешно завершена\r\n")
}

func start_curl(url string, out_file string) int {
	var exit_code int
	cmd := exec.Command("curl", url, "-o", out_file)
	//cmd.Stdout = os.Stdout
	//cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		exit_code = int(err.(*exec.ExitError).Sys().(syscall.WaitStatus).ExitCode)
	} else {
		exit_code = 0
	}
	return exit_code
}

На сегодня все. Компилятор соберет нам готовый exe-файл. Бояться большого размера (несколько мегабайт) не нужно. В этот файл будут собраны минимальная среда исполнения и все необходимые пакеты. Потребление памяти приложением на Go раза в два меньше чем у perl или python(если мы конечно говорим о небольших приложениях). Если статья кого-то заинтересовала, в комментариях укажите, какую из тем хотелось бы рассмотреть:

  • работа с текстом (парсинг stdout, кодировки)
  • файлы(информация, поиск, ведение логов)
  • работа с winapi(подключение dll, вызов функций, обработка ответов)
  • работа с adodb(как прочитать данные из базы MSAccess)
  • отправка e-mail средствами Go
  • простой RPC-сервер
Поделиться публикацией
Комментарии 45
    +10
    По-моему это из пушки по воробьям.

    В unix такой проблемы нет, shell-скрипты могут быть полноценными программами (не без огороворок, но простейшее скачивание файлов делается элементарно). Потребление памяти для скриптов, запускаемых редко — вообще не проблема.

      +2
      Прочитал заметку по диагонали, и насколько понял, рассматриваемая задача приведена просто для примера и не говорится, что именно она невозможна через shell-скрипт. Ну а скрипты на go/python вполне себе имеют смысл, если приходится выполнять задачи на различных ос
        0
        Хендлинг ошибок в баше и чуть более сложная логика — это боль.
        Кроме того, для unix и windows данный скрипт пришлось бы писать отдельно, решение с Go кроссплатформенно.
          0
          ну да, кросплатформенно, с точностью до обратных слешей :-)
      +4
      Offtopic: побороть cmd.exe вам поможет delayed expansion. Пример:

      setlocal enabledelayedexpansion
      
      echo %time%
      ping 127.0.0.1 -n 1 -w 1000 >nul 2>&1
      echo %time%
      if 1 neq 0 (
        echo %time% !time!
        ping 127.0.0.1 -n 1 -w 1000 >nul 2>&1
        echo %time% !time!
      )
      
        +6
        На что только не пойдут люди, что бы PowerShell не изучать!

        :)

          +4
          Однако если потом захочется перейти на *nix то данный опыт позволит ещё не изучать ещё и shell-скрипты.
            +6
            очень врядли :)
              +1
              Вы так говорите, как будто это хорошо.
                0
                он просто ленивый разработчик.

                но есть трудолюбивые, да.
              +1
              На что только люди идут, чтобы не ставить Cygwin и работать в одной среде независимо от платформы вообще… Ведь не windows единым жив админ…
                0
                Вам приходилось поддерживать целую ферму Cygwin? Мне вот с одним Curl проблем хватает. Буквально вчера пришлось обновлять версию, так как в одном филиале NAT средствами Windows организован.
                0
                Я ничего не писан на PowerShell, но писал на C#. Код на PowerShell будет зависеть от версии PowerShell на конечной машине? Если нет, то смогу ли я выполнить код на PowerShell в установке WinXP или Win2003 по-умолчанию или мне придется проверить версию и доустановить необходимое?
                +2
                В качестве примера куда лучше подошёл бы случай, в котором сложность кода при переписывании на Go уменьшается.
                  0
                  В примере я старался наиболее точно воспроизвести логику работы. Выгода появляется при росте сложности, например думаю у 50-строчного СMD/BAT (мне и такие писать приходилось) выиграю.
                  +6
                  Чем это лучше питонистых скриптов?
                    +2
                    Не надо ставить питон на целевую машину.
                      +1
                      Это сарказм?
                      Емнип, в nix* мире (разве что за исключением embedded) python в большинстве случаев уже стоит. А вот тот же go ещё нужно поставить.
                      А в win мире какая разница что ставить, если всё равно нужно ставить?
                        +2
                        под скомпилированный файл ставить то ничего не нужно, среду необходимо поставить единожды на компьютере разработчика
                          0
                          Во-первых, это аргумент автора. Во-вторых, речь о винде. В-третьих, standalone-экзешник не нуждается в установленном на целевую машину Go.
                            –1
                            так вроде из питон скрипта можно тоже exe сделать?
                              0
                              весом в 20 мег примерно
                        +1
                        Ничем. Если вам удобно писать на python. Где-то читал, что при выборе языка для конкретной задачи, берется тот в котором код меньше и понятнее. Лично мне, например нравится классический sh, но на нем tcp-сервер c tls и авторизацией не напишешь. Насчет питона, классический «однострочный http-сервер» съедает 18 мегабайт памяти. Пятистрочный на Go — три.
                        +8
                        А зачем скачивать файлики в программе на Go, через отдельную утилиту?
                        Почему нельзя было средствами Go скачать?
                          0
                          Скачивание файликов здесь для примера. Я знаю про go-curl. Но использование его слишком усложняет и увеличивает код, а я писал статью для начинающих. Кроме того, в go-curl C-шные вставки. Пришлось бы рассказывать про mingw и как настроить liteide на работу с ним. По этой же причине я пока не заикался про go-sqlite3. Иногда все-же проще притащить на целевую машину готовый экзэшник, особенно если из возврата нужен только код завершения.
                          +4
                          out_file := ".\\test\\test.zip" //В Go бэкслэш используется для экранирования, так что в пути windows его придется удваивать
                          Ну так используйте другие кавычки:
                          out_file := `.\test\test.zip`
                            +3
                            … а еще windows в функциях работы прекрасно кушает / как разделитель пути…
                              0
                              Не все. Я встречал приложения, которым \, иначе «путь не найден».
                                0
                                Я говорю про функции системы, а не приложения. Приложения многие ругаются, да…
                            +2
                            > В этот файл будут собраны минимальная среда исполнения и все необходимые пакеты.

                            Это не так — curl то в него не попадет, и его нужно будет отдельно таскать. Или в Windows он встроенный?
                              0
                              Каюсь. Curl использовал для примера, но если кто-то собрался использовать, то киньте curl.exe в папку с программой.
                                0
                                Да, собтвенно, курл здесь и не нужен. Можно воспользоваться встроенными модулями Go.
                              0
                              понравилось слово goroutines
                              и так как это — одна из киллер фич языка, думаю будет интересно увидеть продолжение именно об этом(concurrency)
                                0
                                Про это уже писали и не раз. Может позже…
                                0
                                Тоже использую Go для административных скриптов иногда, запускаемых через shebang (#!) — одна из проблем, это заморочки с GOPATH, если нужно использовать сторонние пакаджи (mysql, к примеру). Но для большинства задач — намного более приятный опыт и уверенность в коде, нежели с bash.
                                  0
                                  В linux я примерно так запускаю:

                                  $ cat ~/devel/start_liteide_64.sh

                                  export GOROOT=$HOME/devel/go64
                                  export GOPATH=$HOME/devel
                                  export PATH=$PATH:$GOROOT/bin
                                  exec $HOME/devel/liteide/bin/liteide

                                  а для 86 у меня отдельный скрипт соответственно GOPATH общий. Но заметил одну деталь, при компиляции возможность запуска зависит от версии glibc. Так что приложение собранное на машине с debian пойдет и для ubuntu, но не наоборот.
                                    0
                                    Возможно в Go 1.5 эта проблема исчезнет — там будет новый компилятор.
                                      0
                                      Нет-нет, проблема в другом. Есть скрипт, который я хочу запускать через gorun. go и gorun поставить на сервер — не проблема, и все что мне нужно — чтобы скрипт можно было запускать, как, ну, обычный sh-скрипт. Но если мне нужен доступ к myqsql из этого Go-скрипта (или любой third-party package), то уже не получится просто так запускать — нужно на целевом сервере (в моем случае их много) устанавливать GOPATH и go get-ить туда нужный package, чтобы скрипт работал.
                                      Ну, это больше не проблема, это так — заморочка. )
                                    0
                                    В моем случае. Нужно просто с целевой машиной синхронизировать каталог devel/src. Ну и опять же в скрипте запуска указать GOPATH. Но проще скомпилировать.
                                      0
                                      А можно еще в курс включить более практическое:
                                      Имеем Excel-файл, где в первом столбце список устройств (объединение из 3х ячеек);
                                      Осуществить поиск нужной ячейки;
                                      Требуется прочитать второй столбец (собственно те самые 3 ячейки — ip/netmask/gw);
                                      В третий столбец записать некую строку.

                                      Powershell мне в работе с excel совсем не понравился.

                                      Еще вопрос — А как с работой с устройством по telnet/ssh/console — возможности есть?
                                      Как насчет expect-like работы — вводим строку в терминал, дожидаемся ключевого слова, читаем вывод, на основе этого — даем следующую команду.
                                        0
                                        мне бы было интересно почитать про отсылку/прием e-mail через го.
                                          +1
                                          Вы же понимаете, что поставили задачу не для новичков? Если я правильно понял, то прочитать значение из excel вам поможет github.com/mattn/go-ole. Там есть пример работы именно с excel.А вобще я бы сохранил лист с csv. Его читать гораздо проще.
                                            0
                                            Были бы интересны в первую очередь первые два пункта TODO. Хотя все остальное не менее интересно.

                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                            Самое читаемое