Skip to content

Commit

Permalink
Validate deploy.yaml with json-schema
Browse files Browse the repository at this point in the history
  • Loading branch information
kjellberg committed Feb 5, 2023
1 parent 9aa57dd commit 6a7fabd
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 37 deletions.
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gemspec

gem "debug"
gem "json-schema"
gem "mocha"
gem "railties"
gem "railties"
6 changes: 6 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ GEM
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
builder (3.2.4)
concurrent-ruby (1.1.10)
crass (1.0.6)
Expand All @@ -42,6 +44,8 @@ GEM
io-console (0.6.0)
irb (1.6.2)
reline (>= 0.3.0)
json-schema (3.0.0)
addressable (>= 2.8)
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
Expand All @@ -58,6 +62,7 @@ GEM
racc (~> 1.4)
nokogiri (1.14.0-x86_64-linux)
racc (~> 1.4)
public_suffix (5.0.1)
racc (1.6.2)
rack (2.2.5)
rack-test (2.0.2)
Expand Down Expand Up @@ -97,6 +102,7 @@ PLATFORMS

DEPENDENCIES
debug
json-schema
mocha
mrsk!
railties
Expand Down
33 changes: 10 additions & 23 deletions lib/mrsk/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require "pathname"
require "erb"
require "net/ssh/proxy/jump"
require "json-schema"

class Mrsk::Configuration
delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :raw_config, allow_nil: true
Expand Down Expand Up @@ -38,9 +39,10 @@ def destination_config_file(base_config_file, destination)
end

def initialize(raw_config, version: "missing", validate: true)
validate!(raw_config) if validate
@raw_config = ActiveSupport::InheritableOptions.new(raw_config)
@version = version
valid? if validate
ensure_env_available
end


Expand Down Expand Up @@ -122,12 +124,6 @@ def ssh_options
{ user: ssh_user, proxy: ssh_proxy, auth_methods: [ "publickey" ] }.compact
end


def valid?
ensure_required_keys_present && ensure_env_available
end


def to_h
{
roles: role_names,
Expand All @@ -145,24 +141,15 @@ def to_h
}.compact
end

def validate!(config)
schema_file_path = File.join(File.dirname(File.expand_path(__FILE__)), "configuration/schema.yaml")
schema = YAML.load(IO.read(schema_file_path))
JSON::Validator.validate!(schema, config)
rescue JSON::Schema::ValidationError => e
raise Mrsk::Configuration::Error, e.message # Temporary to pass tests
end

private
# Will raise ArgumentError if any required config keys are missing
def ensure_required_keys_present
%i[ service image registry servers ].each do |key|
raise ArgumentError, "Missing required configuration for #{key}" unless raw_config[key].present?
end

if raw_config.registry["username"].blank?
raise ArgumentError, "You must specify a username for the registry in config/deploy.yml"
end

if raw_config.registry["password"].blank?
raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
end

true
end

# Will raise KeyError if any secret ENVs are missing
def ensure_env_available
Expand Down
1 change: 1 addition & 0 deletions lib/mrsk/configuration/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
class Mrsk::Configuration::Error < StandardError; end
70 changes: 70 additions & 0 deletions lib/mrsk/configuration/schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
type: object
required:
- service
- image
- servers
- registry
properties:
service:
type: string
description: Name of the service.
image:
type: string
description: Docker image name.
registry:
type: object
required:
- username
- password
properties:
server:
type: string
description: Docker registry server.
username:
type: string
description: Username for the Docker registry.
password:
type: string
description: Password for the Docker registry.
env:
anyOf:
- type: object
description: Environment variables for the service.
additionalProperties:
type: string
- type: object
description: Grouped by environment
properties:
clear:
type: object
description: Clear environment variables for the service.
additionalProperties:
type: string
secret:
type: array
description: Secret environment variables for the service.
items:
type: string
servers:
anyOf:
- type: array
description: List of servers.
items:
type: string
- type: object
description: Grouped by environment
required:
- web
properties:
web:
type: array
description: List of servers.
items:
type: string
patternProperties:
"^S_":
type: array
description: List of servers.
items:
type: string
27 changes: 14 additions & 13 deletions test/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@ class ConfigurationTest < ActiveSupport::TestCase
ENV["RAILS_MASTER_KEY"] = nil
end

test "ensure valid keys" do
assert_raise(ArgumentError) do
Mrsk::Configuration.new(@deploy.tap { _1.delete(:service) })
Mrsk::Configuration.new(@deploy.tap { _1.delete(:image) })
Mrsk::Configuration.new(@deploy.tap { _1.delete(:registry) })

Mrsk::Configuration.new(@deploy.tap { _1[:registry].delete("username") })
Mrsk::Configuration.new(@deploy.tap { _1[:registry].delete("password") })
end
end

test "roles" do
assert_equal %w[ web ], @config.roles.collect(&:name)
assert_equal %w[ web workers ], @config_with_roles.roles.collect(&:name)
Expand Down Expand Up @@ -132,8 +121,20 @@ class ConfigurationTest < ActiveSupport::TestCase
end
end

test "valid config" do
assert @config.valid?
test "configuration schema is valid" do
cwd = File.dirname(File.expand_path(__FILE__))
metaschema_file_path = File.join(cwd, "/fixtures/files/draft-04-schema.json")
metaschema = JSON.parse(IO.read(metaschema_file_path))

schema_file_path = File.join(cwd, "../lib/mrsk/configuration/schema.yaml")
schema = YAML.load(IO.read(schema_file_path))

assert JSON::Validator.validate(metaschema, schema)
end

test "configuration schema raises errors on fail" do
assert_raise(Mrsk::Configuration::Error) { Mrsk::Configuration.new(@deploy.tap { _1.delete(:service) }) }
assert_raise(Mrsk::Configuration::Error) { Mrsk::Configuration.new(@deploy.tap { _1[:registry].delete("username") }) }
end

test "ssh options" do
Expand Down
149 changes: 149 additions & 0 deletions test/fixtures/files/draft-04-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
{
"id": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
},
"simpleTypes": {
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}
},
"type": "object",
"properties": {
"id": {
"type": "string"
},
"$schema": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
},
"maxLength": { "$ref": "#/definitions/positiveInteger" },
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
},
"maxItems": { "$ref": "#/definitions/positiveInteger" },
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"dependencies": {
"exclusiveMaximum": [ "maximum" ],
"exclusiveMinimum": [ "minimum" ]
},
"default": {}
}

0 comments on commit 6a7fabd

Please sign in to comment.