Promote new Fuel-CLI

Remove cli functions, update README, add new CLI promotion.
Closes-Bug: #1258472

Change-Id: Ie247dbddd84b63166087a9caddc5b6ea38f77377
This commit is contained in:
Vladimir 2013-12-10 08:11:47 +04:00
parent d14e9d2475
commit ae026938f2
7 changed files with 8 additions and 845 deletions

View File

@ -17,7 +17,7 @@ Deploy
OpenStack installation in the desired configuration on the selected nodes.
Astute uses data about nodes and deployment settings and recalculates parameters needed for deployment. Calculated parameters are passed to the nodes being deployed by use of nailyfact MCollective agent that uploads these attributes to `/etc/naily.facts` file of the node. Then puppet parses this file using Facter plugin and uploads these facts into puppet. These facts are used during catalog compilation phase by puppet master. Finally catalog is executed and Astute orchestrator passes to the next node in deployment sequence. Fuel Library provides puppet modules for Astute.
Astute uses data about nodes and deployment settings and recalculates parameters needed for deployment. Calculated parameters are passed to the nodes being deployed by use of nailyfact MCollective agent that uploads these attributes to `/etc/astute.yaml` file of the node. Then puppet parses this file using Facter plugin and uploads these facts into puppet. These facts are used during catalog compilation phase by puppet. Finally catalog is executed and Astute orchestrator passes to the next node in deployment sequence. Fuel Library provides puppet modules for Astute.
Using as library
-----
@ -46,23 +46,14 @@ orchestrator.watch_provision_progress(reporter, environment['task_uuid'], enviro
orchestrator.deploy(reporter, environment['task_uuid'], environment['nodes'])
```
More information about expected content of environment you can find here:
http://docs.mirantis.com/fuel/3.1/installation-fuel-cli.html#yaml-high-level-structure
Simple example of using Astute as library: https://github.com/Mirantis/astute/blob/master/bin/astute
Example of using Astute as library: https://github.com/stackforge/fuel-web/blob/master/naily/lib/naily/dispatcher.rb
Using as CLI
-----
Provision:
astute -f simple.yaml -c provision
CLI interface in Astute no longer supported. Please use new Fuel-CLI. More details you can get by link: https://github.com/Mirantis/fuel-docs/blob/master/pages/user-guide/cli.rst
More information about content of `simple.yaml` you can find here: http://docs.mirantis.com/fuel/3.1/installation-fuel-cli.html#yaml-high-level-structure
Additional materials
-----
- ISO, other materials: http://fuel.mirantis.com/

View File

@ -14,7 +14,6 @@ Gem::Specification.new do |s|
s.add_dependency 'mcollective-client', '>= 2.3.1'
s.add_dependency 'symboltable', '1.0.2'
s.add_dependency 'rest-client', '~> 1.6.7'
s.add_dependency 'kwalify', '~> 0.7.2'
s.add_development_dependency 'rake', '10.0.4'
s.add_development_dependency 'rspec', '2.13.0'

View File

@ -14,99 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
if RUBY_VERSION < "1.9"
puts "Astute tested and works only on Ruby 1.9.3 but you use #{RUBY_VERSION}"
puts "If you still want to try it on older versions of ruby, try 'ruby -rubygems bin/astute'"
end
puts <<-EOF
CLI interface in Astute no longer supported. Please use new Fuel-CLI.
More details you can get by link: https://github.com/Mirantis/fuel-docs/blob/master/pages/user-guide/cli.rst
EOF
require 'optparse'
require 'yaml'
require 'astute'
require 'astute/version'
require 'astute/cli/enviroment'
SUCCESS = 0
FAIL = 1
ERROR_CODE_COMMAND_USAGE = 64
class ConsoleReporter
def report(msg)
puts msg.inspect
end
end
def report_and_exit(exception, verbose)
$stderr.puts "Error: #{exception.inspect}"
unless verbose
puts "Hint: use astute with --verbose or check log (#{Astute::LOG_PATH}) for more details"
end
Astute.logger.error exception.format_backtrace
exit FAIL
end
def exit_code(status)
status.each { |node| return FAIL if node['status'] == 'error' }
SUCCESS
end
opts = {}
optparse = OptionParser.new do |o|
o.banner = "Usage: bin/astute -c COMMAND -f FILENAME "
o.on("-v", "--[no-]verbose", "Run verbosely") do |v|
opts[:verbose] = v
end
o.on("-f FILENAME", "Environment in YAML format. Samples are in examples directory.") do |f|
opts[:filename] = f
end
o.on_tail("-h", "--help", "Show this message") { puts o; exit }
o.on_tail("--version", "Show version") { puts Astute::VERSION; exit }
o.on("-c", "--command COMMAND", [:provision],
"Select operation: provision") do |c|
opts[:command] = c
end
end
optparse.parse!(ARGV)
if opts[:filename].nil?
puts optparse
exit ERROR_CODE_COMMAND_USAGE
end
reporter = ConsoleReporter.new
Astute.logger = Logger.new(STDOUT) if opts[:verbose]
begin
environment = Astute::Cli::Enviroment.new(opts[:filename], opts[:command])
rescue Errno::ENOENT, Psych::SyntaxError, Astute::Cli::Enviroment::ValidationError => e
report_and_exit(e, opts[:verbose])
end
if environment['attributes'] && environment['attributes']['deployment_engine']
case environment['attributes']['deployment_engine']
when 'nailyfact'
deploy_engine = Astute::DeploymentEngine::NailyFact
end
end
deploy_engine ||= nil
orchestrator = Astute::Orchestrator.new(deploy_engine, log_parsing=true)
def console_provision(orchestrator, reporter, environment)
orchestrator.provision(reporter, environment['engine'], environment['nodes'])
puts "restarting nodes..."
sleep 5
puts "start watching progress"
exit_code orchestrator.watch_provision_progress(reporter, environment['task_uuid'], environment['nodes'])
end
begin
exit console_provision(orchestrator, reporter, environment) if opts[:command] == :provision
rescue => e
report_and_exit(e, opts[:verbose])
end
exit 1

View File

@ -1,117 +0,0 @@
---
# Base config
task_uuid: deployment_task
engine:
url: http://localhost/cobbler_api
username: cobbler
password: cobbler
# These parameters can be overridden in the specification of a particular node
common_node_settings:
name_servers: "10.20.0.2"
profile: centos-x86_64
# These parameters can be overridden in the specification of a particular node
common_power_info:
power_type: ssh
power_user: root
power_pass: /root/.ssh/bootstrap.rsa
netboot_enabled: 1
# These parameters can be overridden in the specification of a particular node
common_ks_meta:
mco_enable: 1
mco_vhost: mcollective
mco_pskey: unset
mco_user: mcollective
puppet_enable: 0
install_log_2_syslog: 1
mco_password: marionette
puppet_auto_setup: 1
puppet_master: fuel.domain.tld
mco_auto_setup: 1
auth_key: '""'
puppet_version: 2.7.19
mco_connector: rabbitmq
mco_host: 10.20.0.2
# Nodes
nodes:
- name: controller-8
hostname: controller-8.domain.tld
# Data for provision
ks_meta:
# ks_spaces: '"[{"type": "disk", "id": "disk/by-path/pci-0000:00:0d.0-scsi-0:0:0:0",
# "volumes": [{"type": "boot", "size": 300}, {"mount": "/boot", "type": "raid",
# "size": 200}, {"type": "lvm_meta", "name": "os", "size": 64}, {"size": 11264,
# "type": "pv", "vg": "os"}, {"type": "lvm_meta", "name": "image", "size": 64},
# {"size": 4492, "type": "pv", "vg": "image"}], "size": 16384}]"'
ks_disks:
# All size should be set in megabytes
- type: "disk"
id: "disk/by-path/pci-0000:00:0d.0-scsi-0:0:0:0"
size: 16384
volumes:
- type: "boot"
size: 300
- type: "raid"
mount: "/boot"
size: 200
- type: "lvm_meta"
name: "os"
size: 64
- type: "pv"
size: 11264
vg: os
- type: "pv"
vg: "image"
size: 4492
- type: "vg"
id: "os"
min_size: 11264
label: "Base System"
volumes:
- type: "lv"
mount: "/"
name: root
size: 10048
- type: "lv"
mount: "/swap"
name: swap
size: 1024
- type: "vg"
id: "image"
min_size: 4492
label: "Image Storage"
volumes:
- type: "lv"
mount: "/var/lib/glance"
name: glance
size: 4200
interfaces:
- name: eth2
ip_address: 10.20.0.187
netmask: 255.255.255.0
static: 0
mac_address: '08:00:27:31:09:34'
onboot: 'no'
peerdns: 'no'
- name: eth1
ip_address: 10.20.0.186
netmask: 255.255.255.0
static: 0
mac_address: 08:00:27:93:54:B0
onboot: 'no'
peerdns: 'no'
- name: eth0
#ip_address: 10.20.0.49 # ip, power_address
#netmask: 255.255.255.0
dns_name: controller-8.domain.tld # fqdn
static: 1
mac_address: 08:00:27:1D:28:71 # mac
onboot: 'yes'
peerdns: 'no'
use_for_provision: true
#End data for provision

View File

@ -1,317 +0,0 @@
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
require 'yaml'
require 'rest-client'
require 'astute/ext/hash'
require 'astute/cli/enviroment'
require 'astute/cli/yaml_validator'
module Astute
module Cli
class Enviroment
POWER_INFO_KEYS = ['power_type', 'power_user', 'power_pass', 'netboot_enabled']
ID_KEYS = ['id', 'uid']
COMMON_NODE_KEYS = ['name_servers', 'profile']
KS_META_KEYS = ['mco_enable', 'mco_vhost', 'mco_pskey', 'mco_user', 'puppet_enable',
'install_log_2_syslog', 'mco_password', 'puppet_auto_setup', 'puppet_master',
'mco_auto_setup', 'auth_key', 'puppet_version', 'mco_connector', 'mco_host']
NETWORK_KEYS = ['ip', 'mac', 'fqdn']
PROVISIONING_NET_KEYS = ['power_address']
PROVISION_OPERATIONS = [:provision]
def initialize(file, operation)
@config = YAML.load_file(file)
validate_enviroment(operation)
to_full_config(operation)
end
def [](key)
@config[key]
end
private
def to_full_config(operation)
@config['nodes'].each do |node|
# Common section
define_id_and_uid(node)
# Provision section
if PROVISION_OPERATIONS.include? operation
node['meta'] ||= {}
define_network_ids(node)
define_power_address(node)
define_interfaces_and_interfaces_extra(node)
define_ks_spaces(node)
define_power_info(node)
define_ks_meta(node)
define_node_settings(node)
define_disks_section(node)
end
end
end
def validate_enviroment(operation)
validator = YamlValidator.new(operation)
errors = validator.validate(@config)
errors.each do |e|
if e.message.include?("is undefined")
Astute.logger.warn "[#{e.path}] #{e.message}"
else
Astute.logger.error "[#{e.path}] #{e.message}"
$stderr.puts "[#{e.path}] #{e.message}"
end
end
if errors.select {|e| !e.message.include?("is undefined") }.size > 0
raise Enviroment::ValidationError, "Environment validation failed"
end
end
# Get data about discovered nodes using FuelWeb API
def find_node_api_data(node)
@api_data ||= begin
response = RestClient.get 'http://localhost:8000/api/nodes'
@api_data = JSON.parse(response).freeze
end
if node['mac']
api_node = @api_data.find{ |n| n['mac'].upcase == node['mac'].upcase }
return api_node if api_node
end
raise Enviroment::ValidationError, "Node #{node['name']} with mac address #{node['mac']}
not find among discovered nodes"
end
# Set uniq id and uid for node from Nailgun using FuelWeb API
def define_id_and_uid(node)
id = find_node_api_data(node)['id']
# This params set for node by Nailgun and should not be edit by user
node.merge!(
'id' => id,
'uid' => id
)
end
# Set meta/disks section for node. This data used in provision to calculate the percentage
# completion of the installation process.
# Example result for node['meta']
# "disks": [
# {
# "model": "VBOX HARDDISK",
# "disk": "disk/by-path/pci-0000:00:0d.0-scsi-0:0:0:0",
# "name": "sda",
# "size": 17179869184
# }...
# ]
def define_disks_section(node)
node['meta']['disks'] = find_node_api_data(node)['meta']['disks']
end
def define_parameters(node, config_group_name, keys, position=nil)
position ||= node
if @config[config_group_name]
config_group = @config[config_group_name]
keys.each do |key|
position.reverse_merge!(key => config_group[key])
end
end
absent_keys = position.absent_keys(keys)
if !absent_keys.empty?
raise Enviroment::ValidationError, "Please set #{config_group_name} block or
set params for #{node['name']} manually #{absent_keys.each {|k| p k}}"
end
@config.delete(config_group)
end
# Add common params from common_node_settings to every node. Already certain parameters will not be changed.
def define_node_settings(node)
define_parameters(node, 'common_node_settings', COMMON_NODE_KEYS)
end
# Add common params from common_power_info to every node. Already certain parameters will not be changed.
def define_power_info(node)
define_parameters(node, 'common_power_info', POWER_INFO_KEYS)
end
# Add common params from common_ks_meta to every node. Already certain parameters will not be changed.
def define_ks_meta(node)
define_parameters(node, 'common_ks_meta', KS_META_KEYS, node['ks_meta'])
end
# Add duplicates network params to node: ip, mac, fqdn
def define_network_ids(node)
network_eth = node['interfaces'].find {|eth| eth['use_for_provision'] } rescue nil
if network_eth
if network_eth['ip_address'].blank?
node['mac'] = network_eth['mac_address']
api_node = find_node_api_data(node)
api_provision_eth = api_node['meta']['interfaces'].find { |n| n['mac'].to_s.upcase == network_eth['mac_address'].to_s.upcase }
network_eth['ip_address'] = api_provision_eth['ip']
network_eth['netmask'] = api_provision_eth['netmask']
end
node.reverse_merge!(
'ip' => network_eth['ip_address'],
'mac' => network_eth['mac_address'],
'fqdn' => network_eth['dns_name']
)
network_eth.delete('use_for_provision')
end
absent_keys = node.absent_keys(NETWORK_KEYS)
if !absent_keys.empty?
raise Enviroment::ValidationError, "Please set 'use_for_provision' parameter
for #{node['name']} or set manually #{absent_keys.each {|k| p k}}"
end
end
# Add duplicates network params to node: power_address
def define_power_address(node)
node['power_address'] = node['ip'] or raise Enviroment::ValidationError, "Please
set 'power_address' parameter for #{node['name']}"
end
# Extend blocks interfaces and interfaces_extra to old formats:
# interfaces:
# eth0:
# ip_address: 10.20.0.188
# netmask: 255.255.255.0
# dns_name: controller-22.domain.tld
# static: '0'
# mac_address: 08:00:27:C2:06:DE
# interfaces_extra:
# eth0:
# onboot: 'yes'
# peerdns: 'no'
def define_interfaces_and_interfaces_extra(node)
return if [node['interfaces'], node['extra_interfaces']].all? {|i| i.is_a?(Hash)}
formated_interfaces = {}
interfaces_extra_interfaces = {}
node['interfaces'].each do |eth|
formated_interfaces[eth['name']] = eth
formated_interfaces[eth['name']].delete('name')
interfaces_extra_interfaces[eth['name']] = {
'onboot' => eth['onboot'],
'peerdns' => eth['onboot']
}
end
node['interfaces'] = formated_interfaces
node['extra_interfaces'] = interfaces_extra_interfaces
end
# Add duplicate param 'fqdn' to node if it is not specified
def define_fqdn(node)
node['fqdn'] ||= find_node_api_data(node)['meta']['system']['fqdn']
end
# Add meta/interfaces section for node:
# meta:
# interfaces:
# - name: eth0
# ip: 10.20.0.95
# netmask: 255.255.255.0
# mac: 08:00:27:C2:06:DE
# max_speed: 100
# current_speed: 100
def define_meta_interfaces(node)
node['meta']['interfaces'] = find_node_api_data(node)['meta']['interfaces']
end
# Add network_data section for node:
# network_data:
# - dev: eth1
# ip: 10.108.1.8
# name: public
# netmask: 255.255.255.0
# - dev: eth0
# ip: 10.108.0.8
# name:
# - management
# - storage
def define_network_data(node)
return if node['network_data'].is_a?(Array) && !node['network_data'].empty?
node['network_data'] = []
# If define_interfaces_and_interfaces_extra was call or format of config is full
if node['interfaces'].is_a?(Hash)
node['interfaces'].each do |key, value|
node['network_data'] << {
'dev' => key,
'ip' => value['ip_address'],
'name' => value['network_name'],
'netmask' => value['netmask']
}
end
else
node['interfaces'].each do |eth|
node['network_data'] << {
'dev' => eth['name'],
'ip' => eth['ip_address'],
'name' => eth['network_name'],
'netmask' => eth['netmask']
}
end
end
end
# Generate 'ks_spaces' param from 'ks_disks' param in section 'ks_meta'
# Example input for 'ks_disks' param:
# [{
# "type"=>"disk",
# "id"=>"disk/by-path/pci-0000:00:0d.0-scsi-0:0:0:0",
# "size"=>16384,
# "volumes"=>[
# {
# "type"=>"boot",
# "size"=>300
# },
# {
# "type"=>"pv",
# "size"=>16174,
# "vg"=>"os"
# }
# ]
# }]
# Example result for 'ks_spaces' param: "[{"type": "disk", "id": "disk/by-path/pci-0000:00:0d.0-scsi-0:0:0:0", "volumes": [{"type": "boot", "size": 300}, {"mount": "/boot", "type": "raid", "size": 200}, {"type": "lvm_meta", "name": "os", "size": 64}, {"size": 11264, "type": "pv", "vg": "os"}, {"type": "lvm_meta", "name": "image", "size": 64}, {"size": 4492, "type": "pv", "vg": "image"}], "size": 16384}]"
def define_ks_spaces(node)
if node['ks_meta']['ks_spaces'].present?
node['ks_meta'].delete('ks_disks')
return
end
if node['ks_meta']['ks_disks'].blank?
raise Enviroment::ValidationError, "Please set 'ks_disks' or 'ks_spaces' parameter
in section ks_meta for #{node['name']}"
end
node['ks_meta']['ks_spaces'] = '"' + node['ks_meta']['ks_disks'].to_json.gsub("\"", "\\\"") + '"'
node['ks_meta'].delete('ks_disks')
end
end # class end
class Enviroment::ValidationError < StandardError; end
end # module Cli
end

View File

@ -1,263 +0,0 @@
type: map
mapping:
"task_uuid":
type: text
"engine":
type: map
required: true
desc: Cobbler engine credentials
mapping:
"url":
type: text
required: true
"username":
type: text
required: true
"password":
type: text
required: true
"common_power_info":
type: map
mapping:
"power_type":
type: text
required: true
desc: Cobbler power-type. Consult cobbler documentation for available options.
"power_user":
type: text
required: true
desc: Username for cobbler to manage power of this machine
"power_pass":
type: text
required: true
desc: Password/credentials for cobbler to manage power of this machine
"netboot_enabled":
type: int
required: true
desc: Disable/enable netboot for this node.
range: { min: 0, max: 1 }
"common_node_settings":
type: map
mapping:
"name_servers":
type: text
required: true
"profile":
type: text
enum: ["centos-x86_64", "ubuntu_1204_x86_64", 'rhel-x86_64']
desc: Cobbler profile for the node.
"common_ks_meta":
type: map
mapping:
"mco_enable":
type: int
range: { min: 0, max: 1 }
required: true
"mco_vhost":
type: text
required: true
"mco_pskey":
type: text
required: true
"mco_user":
type: text
required: true
"mco_password":
type: text
required: true
"puppet_enable":
type: int
range: { min: 0, max: 1 }
required: true
"puppet_auto_setup":
type: int
range: { min: 0, max: 1 }
required: true
"puppet_master":
type: text
required: true
"mco_auto_setup":
type: int
range: { min: 0, max: 1 }
required: true
"auth_key":
type: text
required: true
"puppet_version":
type: text
"install_log_2_syslog":
type: int
range: { min: 0, max: 1 }
required: true
"mco_connector":
type: text
required: true
"mco_host":
type: text
required: true
"nodes":
type: seq
required: true
desc: Array of nodes
sequence:
- type: map
mapping:
"id":
type: int
unique: yes
desc: MCollective node id in mcollective server.cfg
"uid":
type: int
unique: yes
desc: UID of the node for deployment engine. Should be equal to `id`
"name":
type: text
required: true
unique: yes
desc: Name of the system in cobbler
"hostname":
type: text
required: true
"fqdn":
type: text
desc: Fully-qualified domain name of the node
"profile":
type: text
enum: ["centos-x86_64", "ubuntu_1204_x86_64", 'rhel-x86_64']
desc: Cobbler profile for the node.
"ip":
type: text
"mac":
type: text
"power_address":
type: text
desc: IP address of the device managing the node power state
"power_type":
type: text
desc: Cobbler power-type. Consult cobbler documentation for available options.
"power_user":
type: text
desc: Username for cobbler to manage power of this machine
"name_servers":
type: text
"power_pass":
type: text
desc: Password/credentials for cobbler to manage power of this machine
"netboot_enabled":
type: int
range: { min: 0, max: 1 }
desc: Disable/enable netboot for this node.
"ks_meta":
type: map
required: true
desc: Kickstart metadata used during provisioning
mapping:
"mco_enable":
type: int
range: { min: 0, max: 1 }
"mco_vhost":
type: text
"mco_pskey":
type: text
"mco_user":
type: text
"mco_password":
type: text
"puppet_enable":
type: int
range: { min: 0, max: 1 }
"puppet_auto_setup":
type: int
range: { min: 0, max: 1 }
"puppet_master":
type: text
"mco_auto_setup":
type: int
range: { min: 0, max: 1 }
"auth_key":
type: text
"puppet_version":
type: text
"install_log_2_syslog":
type: int
range: { min: 0, max: 1 }
"mco_connector":
type: text
"mco_host":
type: text
"ks_spaces":
type: text
"ks_disks":
type: seq
sequence:
- type: map
required: true
mapping:
"type":
type: str
required: true
enum: [disk, vg]
"id":
type: text
required: true
unique: yes
"size":
type: int
"min_size":
type: int
"label":
type: text
"volumes":
type: seq
sequence:
- type: map
mapping:
"type":
type: text
required: true
enum: [lv, pv, partition, mbr, raid, lvm_meta, boot]
"mount":
type: text
"size":
type: int
"vg":
type: text
"name":
type: text
"interfaces":
type: seq
required: true
sequence:
- type: map
mapping:
"name":
type: text
required: true
unique: yes
"ip_address":
type: text
unique: yes
"netmask":
type: text
"dns_name":
type: text
unique: yes
"static":
type: int
range: { min: 0, max: 1 }
"mac_address":
type: text
required: true
unique: yes
"onboot":
type: text
required: true
enum: ['yes', 'no']
"peerdns":
type: text
required: true
enum: ['yes', 'no']
"use_for_provision":
type: bool
default: false
name: use_for_provision

View File

@ -1,40 +0,0 @@
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
require 'kwalify'
module Astute
module Cli
class YamlValidator < Kwalify::Validator
def initialize(operation)
[:provision].include?(operation) ? schemas = [operation] : raise("Incorrect scheme for validation")
schema_hashes = []
schema_dir_path = File.expand_path(File.dirname(__FILE__))
schemas.each do |schema_name|
schema_path = File.join(schema_dir_path, "#{schema_name}_schema.yaml")
schema_hashes << YAML.load_file(schema_path)
end
#p schema_hashes[0].recursive_merge!(schema_hashes[1])
#FIXME: key 'hostname:' is undefined for provision_and_deploy. Why?
@schema = schema_hashes.size == 1 ? schema_hashes.first : schema_hashes[0].deep_merge(schema_hashes[1])
super(@schema)
end
end # YamlValidator
end # Cli
end # Astute