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
          ну да, кросплатформенно, с точностью до обратных слешей :-)
            +1
            Матчасть же.
            golang.org/pkg/path/filepath/
              0
              Между прочим, прямой слэш в Windows — валидный разделитель.
          +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. Хотя все остальное не менее интересно.

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

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