Программа klish предназначенa для организации интерфейса командной строки, в котором оператору доступен только строго определенный набор команд. Это отличает klish от стандартных shell интерпретаторов, где оператор может использовать любые команды, существующие в системе и возможность их выполнения зависит только от прав доступа. Набор доступных в klish команд определяется на этапе конфигурирования и может быть задан, в общем случае, различными способами, основным из которых являются XML файлы конфигурирования. Команды в klish не являются системными командами, а полностью определены в файлах конфигурации, со всеми возможными параметрами, синтаксисом и действиями, которые они выполняют.
Основным применением klish являются встроенные системы, где оператору доступны только определенные, специфические для конкретного устройства, команды, а не произвольный набор команд, как в системах общего назначения. В таких встроенных системах klish может подменить собой shell интерпретатор, недоступный для оператора.
Примером встроенных систем может служить управляемое сетевое оборудование. Исторически в этом сегменте сложилось два основных подхода к организации интерфейса командной строки. Условно их можно назвать подход "Cisco" и подход "Juniper". И Cisco и Juniper имеют два режима работы - командный режим и режим конфигурирования. В командном режиме введенные команды немедленно исполняются, но не влияют на конфигурацию системы. Это команды просмотра состояния, команды управления устройством, но не изменения его конфигурации. В режиме конфигурирования идет настройка оборудования и сервисов. В Cisco команды конфигурирования также исполняются немедленно, меняя конфигурацию системы. В Juniper конфигурацию можно условно представить себе, как текстовый файл, изменения в котором производятся с помощью стандартных команд редактирования. При редактировании изменения не применяются в системе. И только по специальной команде весь накопленный комплекс изменений применяется разом, чем обеспечивается согласованность изменений. При Cisco подходе похожее поведение также можно эмулировать, проектируя команды определенным образом, но для Cisco это менее естественный способ.
Какой из подходов лучше и проще в конкретном случае - определяется задачей. Klish в первую очередь расчитан на подход Cisco, т.е. на немедленно выполняемые команды. Однако проект имеет систему плугинов, что позволяет расширять его возможности. Так плугин klish-plugin-sysrepo, реализованный отдельным проектом, работая на основе хранилища sysrepo, позволяет организовать Juniper подход.
Основа проекта klish - библиотека libklish. На ее основе построены клиент klish и сервер klishd. Основную работу выполняет сервер klishd. Он загружает конфигурацию команд и ожидает запросов от клиентов. Взаимодействие между клиентами и сервером происходит по UNIX-сокетам с использованием специально разработанного для этой цели протокола KTP (Klish Transfer Protocol). Задача клиента - передача ввода от оператора на сервер и получение от него результата для показа оператору. Клиент не знает, какие команды существуют, как их выполнять. Все это делает серверная сторона. Так как клиент имеет относительно простой код, не трудно реализовать альтернативные программы - клиенты, например графический клиент или клиент для автоматизированного управления. Сейчас написан только текстовый клиент klish. Когда клиент соединяется с сервером, порождается отдельный процесс для обслуживания конкретного клиента. При завершении сессии, процесс также завершается. Таким образом типичное применение klish - это заранее запущенный в системе сервер klishd и клиенты, подключающиеся к нему по мере надобности.
Klish имеет два типа плугинов. Плугины для загрузки конфигурации команд (директория dbs/ в дереве исходных кодов) и плугины, реализующие действия для команд (директория plugins/ в дереве исходных кодов).
Для настройки параметров сервера используется конфигурационный файл '/etc/klish/klishd.conf'. Альтернативный конфигурационный файл можно указать при запуске сервера klishd.
Для настройки параметров клиента используется конфигурационный файл '/etc/klish/klish.conf'. Альтернативный конфигурационный файл можно указать при запуске клиента klish.
В составе klish существуют следующие плугины dbs (DataBases) для загрузки конфигурации команд (схемы):
Существует внутреннее представление схемы, совпадающее с ischeme. Остальные плугины переводят внешние представление в ischeme, а klish обрабатывает ischeme внутренними механизмами.
Установленные плугины dbs находятся в '/usr/lib/klish/dbs' (если конфигурировать сборку с --prefix=/usr). Их имена 'kdb-<имя>.so', например '/usr/lib/klish/dbs/kdb-libxml2.so'.
Каждая команда klish выполняет какое-либо действие или несколько действий сразу. Эти действия надо как-то описывать. Если смотреть внутрь реализации, то klish может запускать только исполняемый откомпилированный код из плугина. Плугины содержат так называемые символы (symbol, sym), которые, по-сути, представляют собой функции с единым зафиксированным API. На эти символы могут ссылаться команды klish. В свою очередь символ может выполнять сложный код, например запускать интерпретатор shell со скриптом, определяемым при описании команды klish в конфигурационном файле. Или же другой символ может исполнять Lua скрипт.
Klish умеет получать символы только из плугинов. Стандартные символы реализованы в плугинах включенных в состав проекта klish. В состав klish входят следующие плугины:
Пользователи могут писать свои плугины и использовать собственные символы в командах klish. Установленные плугины находятся в '/usr/lib/klish/plugins' (если конфигурировать сборку с --prefix=/usr). Их имена 'kplugin-<имя>.so', например '/usr/lib/klish/plugins/kplugin-script.so'.
Основным способом описания klish команд на сегодняшний день являются XML файлы. В данном разделе все примеры будут основаны на XML элементах.
Команды организованы в "области видимости", называемые VIEW. Т.е. каждая команда принадлежит какому-либо VIEW и в нем определена. При работе в klish всегда существует "текущий путь". По умолчанию, при запуске klish текущим путем назначается VIEW с именем "main". Текущий путь определяет какие команды в данный момент видны оператору. Текущий путь можно менять командами навигации. Например перейти в "соседний" VIEW, тогда текущим путем станет этот соседний VIEW, вытеснив старый. Другая возможность - "уйти вглубь", т.е. зайти во вложенный VIEW. Тогда текущий путь станет двух-уровневым, подобно тому, как можно зайти во вложенную директорию в файловой системе. Когда текущий путь имеет более одного уровня, оператору доступны команды самого "глубокого" VIEW, а также команды всех вышележащих уровней. С помощью команд навигации можно выходить из вложенных уровней. При смене текущего пути используемая команда навигации определяет будет ли VIEW текущего пути вытеснен на том же уровне вложенности другим VIEW, либо новый VIEW станет вложенным и в текущем пути появится еще один уровень. То, как VIEW определены в XML файлах не влияет на то, может ли VIEW быть вложенным.
При определении VIEW в XML файлах, одни VIEW могут быть вложены в другие VIEW. Не надо путать эту вложенность с вложенностью при формировании текущего пути. VIEW, определенный внутри другого VIEW добавляет свои команды к командам родительского VIEW, но при этом может адресоваться и отдельно от родительского. Кроме этого существуют ссылки на VIEW. Объявив такую ссылку внутри VIEW, мы добавляем команды того VIEW, на который указывает ссылка, к командам текущего VIEW. Можно определить VIEW со "стандартными" командами и включать ссылку на этот VIEW в другие VIEW, где требуются эти команды, не переопределяя их заново.
В XML файлах конфигурации для объявления VIEW используется тег 'VIEW'.
Команды (тег 'COMMAND') могут иметь параметры (тег 'PARAM'). Команда определяется внутри какого-либо VIEW и пренадлежит ему. Параметры определяются внутри команды и, в свою очередь, принадлежат ей. Команда может состоять только из одного слова, в отличие от команд в klish предыдущих версий. Если нужно определить многословную команду, то создаются вложенные команды. Т.е. внутри команды, идентифицируемой по первому слову многословной команды, создается команда, идентифицируемая по второму слову многословной команды и т.д.
Строго говоря, команда отличается от параметра только тем, что ее значением может быть только заранее определенное слово, в то время, как значением параметра может быть что угодно. Только тип параметра определяет его возможные значения. Таким образом команду можно рассматривать как параметр с типом, который говорит о том, что допустимым значением является само имя параметра. Внутренняя реализация команд именно такая.
В общем случае параметр может определяться непосредственно во VIEW, а не внутри команды, но это скорее нетипичный случай.
Как и VIEW, команды и параметры могут быть ссылками. В этом случае ссылку можно рассматривать просто как подстановку того объекта, на который указывает ссылка.
Параметры могут быть обязательными, опциональными, либо являться обязательным выбором среди нескольких параметров - кандидатов. Таким образом при вводе команды оператором некорые параметры могут быть указаны, а некоторые нет. При разборе командной строки составляется последовательность выбранных параметров.
Тип параметра определяет допустимые значения этого параметра. Обычно типы определяются отдельно с помощью тега 'PTYPE', а параметр ссылается на определенный тип по его имени. Также тип может быть определен прямо внутри параметра, но это не типичный случай, т.к. стандартными типами удается покрыть большую часть потребностей.
Тип PTYPE, как и команда, выполняет определенное действие, заданное тегом 'ACTION'. Действие, заданное для типа, проверяет введенное оператором значение параметра и возвращает результат - успех или ошибка.
Каждая команда должна определять действие, выполняемое при вводе этой команды оператором. Действие может быть одно, либо несколько действий для одной команды. Действие объявляется тегом 'ACTION' внутри команды. В ACTION указывается ссылка на символ (функцию) из плугина функций, которая будет исполнена в данном случае. Все данные внутри тега ACTION доступны символу. Символ по своему усмотрению может использовать эту информацию. В качестве данных, например, может быть задан скрипт, который будет выполнен символом.
Результатом выполнения действия является "код возврата". Он определяет успешность или неуспешность выполнения команды вцелом. Если для одной команды определено более одного действия, то вычисление кода возврата становится более сложной задачей. Каждое действие имеет флаг, определяющий влияет ли код возврата текущего действия на общий код возврата. Так же действия имеют настройки, определяющие будет ли выполняться действие при условии, что предыдущее действие завершилось с ошибкой. Если последовательно выполняются несколько действий, имеющих флаг влияния на общий код возврата, то общим кодом возврата будет код возврата последнего такого действия.