Skip to content

Commit

Permalink
Add Schema validator (#176)
Browse files Browse the repository at this point in the history
* Add schema validation for dip.yml configuration

* Add option to skip validation with env variable

* Remove schema validation references and unused schema file

* Add schema validation instructions for VSCode in README.md

* Add schema validation for dip.yml using ajv

* Remove schema service from docker-compose.yml and dip.yml validation

* Add development dependency on public_suffix gem

* Add public_suffix as dependency in gemspec

* Add comment about public_suffix version compatibility

* Update public_suffix dependency version upper bound
  • Loading branch information
oleander authored Nov 25, 2024
1 parent f8406a2 commit ab7cc37
Show file tree
Hide file tree
Showing 10 changed files with 468 additions and 4 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,27 @@ services:
user: "1000:1000"
```

### dip validate

Validates your dip.yml configuration against the JSON schema. The schema validation helps ensure your configuration is correct and follows the expected format.

```sh
dip validate
```

The validator will check:

- Required properties are present
- Property types are correct
- Values match expected patterns
- No unknown properties are used

If validation fails, you'll get detailed error messages indicating what needs to be fixed.

You can skip validation by setting `DIP_SKIP_VALIDATION` environment variable.

Add `# yaml-language-server: $schema=https://raw.githubusercontent.com/bibendi/dip/refs/heads/master/schema.json` to the top of your dip.yml to get schema validation in VSCode. Read more about [YAML Language Server](https://github.com/redhat-developer/vscode-yaml?tab=readme-ov-file#associating-schemas).

## Changelog

https://github.com/bibendi/dip/releases
6 changes: 5 additions & 1 deletion dip.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ Gem::Specification.new do |spec|

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.glob("lib/**/*") + Dir.glob("exe/*") + %w[LICENSE.txt README.md]
spec.files = Dir.glob("lib/**/*") + Dir.glob("exe/*") + %w[LICENSE.txt README.md schema.json]
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.required_ruby_version = ">= 2.7"

spec.add_dependency "thor", ">= 0.20", "< 2"
spec.add_dependency "json-schema", "~> 5"
# public_suffix >= 6.0 requires Ruby >= 3.0, so we need to specify an upper bound
# to maintain compatibility with Ruby 2.7
spec.add_dependency "public_suffix", ">= 2.0.2", "< 6.0"

spec.add_development_dependency "bundler", ">= 1.15"
spec.add_development_dependency "pry-byebug", "~> 3"
Expand Down
2 changes: 2 additions & 0 deletions dip.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# yaml-language-server: $schema=./schema.json

version: '7'

compose:
Expand Down
131 changes: 131 additions & 0 deletions examples/dip.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
version: '8.1.0'

environment:
RAILS_ENV: development
NODE_ENV: development
DATABASE_URL: postgres://user:password@db:5432/myapp_development
REDIS_URL: redis://redis:6379/0
PORT: ${PORT:-3000}
APP_PORT: ${PORT:-3000}

compose:
files:
- docker-compose.yml
- docker-compose.override.yml
project_name: myapp_project
command: docker compose

interaction:
rails:
description: Run Rails commands
service: web
command: bundle exec rails
default_args: server -p 3000 -b 0.0.0.0
environment:
RAILS_LOG_TO_STDOUT: "true"
compose:
method: run
compose_method: up
run_options:
- service-ports
- rm
profiles:
- web
- development
shell: true
entrypoint: /docker-entrypoint.sh
runner: docker_compose
subcommands:
console:
description: Start Rails console
command: console
routes:
description: Show Rails routes
command: routes
db:
description: Database related commands
subcommands:
migrate:
description: Run database migrations
command: db:migrate
seed:
description: Seed the database
command: db:seed

npm:
description: Run npm commands
service: frontend
command: npm
compose:
method: run
profiles:
- frontend

psql:
description: Connect to PostgreSQL database
service: db
command: psql -h db -U user myapp_development
compose:
method: run
environment:
PGPASSWORD: password

rspec:
description: Run RSpec tests
service: web
command: bundle exec rspec
environment:
RAILS_ENV: test
compose:
method: run
run_options:
- rm
profiles:
- test

shell:
description: Start a shell in the web container
service: web
command: /bin/bash
compose:
method: run
run_options:
- rm

k8s:
description: Run kubectl commands
command: kubectl
runner: kubectl
entrypoint: kubectl
shell: false

brakeman:
description: Check brakeman sast
command: docker run another-image ...

rake:
description: Run Rake tasks
service: web
command: bundle exec rake

provision:
- dip compose down --volumes
- dip compose build
- dip rails db:create
- dip rails db:migrate
- dip rails db:seed
- dip npm install
- dip validate

kubectl:
namespace: myapp-development

modules:
- production

infra:
redis:
git: https://github.com/mycompany/redis-config.git
ref: main
elasticsearch:
path: ./infra/elasticsearch
11 changes: 10 additions & 1 deletion lib/dip/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

module Dip
class CLI < Thor
TOP_LEVEL_COMMANDS = %w[help version ls compose up stop down run provision ssh infra console].freeze
TOP_LEVEL_COMMANDS = %w[help version ls compose up stop down run provision ssh infra console validate]

class << self
# Hackery. Take the run method away from Thor so that we can redefine it.
Expand Down Expand Up @@ -117,6 +117,15 @@ def provision
end
end

desc "validate", "Validate the dip.yml file against the schema"
def validate
Dip.config.validate
puts "dip.yml is valid"
rescue Dip::Error => e
warn "Validation failed: #{e.message}"
exit 1
end

require_relative "cli/ssh"
desc "ssh", "ssh-agent container commands"
subcommand :ssh, Dip::CLI::SSH
Expand Down
29 changes: 27 additions & 2 deletions lib/dip/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require "yaml"
require "erb"
require "pathname"
require "json-schema"

require "dip/version"
require "dip/ext/hash"
Expand Down Expand Up @@ -112,6 +113,24 @@ def to_h
end
end

def validate
raise Dip::Error, "Config file path is not set" if file_path.nil?
raise Dip::Error, "Config file not found: #{file_path}" unless File.exist?(file_path)

schema_path = File.join(File.dirname(__FILE__), "../../schema.json")
raise Dip::Error, "Schema file not found: #{schema_path}" unless File.exist?(schema_path)

data = YAML.load_file(file_path)
schema = JSON.parse(File.read(schema_path))
JSON::Validator.validate!(schema, data)
rescue Psych::SyntaxError => e
raise Dip::Error, "Invalid YAML syntax in config file: #{e.message}"
rescue JSON::Schema::ValidationError => e
data_display = data ? data.to_yaml.gsub("\n", "\n ") : "nil"
error_message = "Schema validation failed: #{e.message}\nInput data:\n #{data_display}"
raise Dip::Error, error_message
end

private

attr_reader :work_dir
Expand All @@ -129,8 +148,8 @@ def config

unless Gem::Version.new(Dip::VERSION) >= Gem::Version.new(config.fetch(:version))
raise VersionMismatchError, "Your dip version is `#{Dip::VERSION}`, " \
"but config requires minimum version `#{config[:version]}`. " \
"Please upgrade your dip!"
"but config requires minimum version `#{config[:version]}`. " \
"Please upgrade your dip!"
end

base_config = {}
Expand All @@ -155,6 +174,12 @@ def config
base_config.deep_merge!(self.class.load_yaml(override_finder.file_path)) if override_finder.exist?

@config = CONFIG_DEFAULTS.merge(base_config)

unless ENV.key?("DIP_SKIP_VALIDATION")
validate
end

@config
end

def config_missing_error(config_key)
Expand Down
Loading

0 comments on commit ab7cc37

Please sign in to comment.