Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create foundation for config deployments #18

Merged
merged 11 commits into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
# TODO

- If the container count changes, it is mixing up stopping with replacing
## DeploymentConfig

- Wrap `Deployment` and `DeploymentConfig` into a package for versioning
- Mount configs
- Don't upload configs if they already exist
- Use `service` instead of `deployment` for config generation?
- Tests should go through the whole flow and actually upload something

## Bugs

- Changing order of role/service definitions should not change the version

## Naming

- Remove `Command::Builder` in favor of embedding commands
- Move `Command::Docker` to `Docker::CLI`
- Rename `Invocation` to `Command`
- Add tests for the DockerCLI classes

## Cleanup

- Is there something like `hosts_for_roles(roles)` needed?

## UX

- Make the console useful for debugging
- I'd want to be able to connect to a host and execute steps as needed
4 changes: 4 additions & 0 deletions lib/cove/configuration/contracts/service_contract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class ServiceContract < Dry::Validation::Contract
required(:target).filled(:string)
end
end
optional(:configs).value(:hash) do
required(:source).filled(:string)
required(:target).filled(:string)
end
end
end
end
Expand Down
3 changes: 3 additions & 0 deletions lib/cove/configuration/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def roles
ports: role_config["ingress"],
mounts: role_config["mounts"],
environment_variables: role_environment,
configs: role_config["configs"],
hosts: role_config["hosts"].map { |host_id| @host_registry[host_id] }
)
end
Expand All @@ -43,6 +44,8 @@ def service_environment
config["environment"] || {}
end

# TODO: Should this be `definition`?
# Config is overloaded at this point
def config
@raw_service_config ||= YAML.safe_load(ERB.new(contents).result)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/cove/deployment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def labels
private

def build_version
Digest::SHA2.hexdigest([role.id, role.image, role.command, role.environment_variables, role.ports].to_json)[0..12]
Digest::SHA2.hexdigest([role.id, role.image, role.command, role.environment_variables, role.ports, role.configs].to_json)[0..12]
end
end
end
43 changes: 43 additions & 0 deletions lib/cove/deployment_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module Cove
class DeploymentConfig
def self.prepare(registry, deployment)
entries = deployment.configs.map do |name, definition|
# TODO: Make sure we always symbolize keys when parsing the config
definition = definition.with_indifferent_access
Entry.new(
registry: registry, deployment: deployment,
name: name, source: definition[:source],
target: definition[:target]
)
end

config_digest = Digest.new
version = config_digest.for(entries.flat_map(&:digestables))

new(deployment: deployment, version: version, entries: entries)
end

def initialize(deployment:, entries:, version:)
@entries = entries
@deployment = deployment
@version = version
@service_path = ::File.join(Cove.host_base_dir, "configs", deployment.service_name)
@host_path = ::File.join(@service_path, version)
end

# TODO: Is `remote_directories` and `remote_files` more applicable?
def host_directories
[@service_path, @host_path] + entries.flat_map(&:host_directories)
end

def files
entries.flat_map(&:files)
end

def entries
@entries.map do |entry|
DeployableEntry.new(host_path: @host_path, entry: entry)
end
end
end
end
17 changes: 17 additions & 0 deletions lib/cove/deployment_config/config_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Cove
class DeploymentConfig
class ConfigFile
attr_reader :path, :content

def initialize(path:, content:)
@path = path
@content = content
end

def directory
dir = File.dirname(path)
dir unless dir == "."
end
end
end
end
22 changes: 22 additions & 0 deletions lib/cove/deployment_config/deployable_entry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Cove
class DeploymentConfig
class DeployableEntry
def initialize(host_path:, entry:)
@base_path = File.join(host_path, entry.name)
@entry = entry
end

def host_directories
[@base_path] + @entry.directories.map do |path|
::File.join(@base_path, path)
end
end

def files
@entry.files.map do |file|
DeployableFile.new(@base_path, file)
end
end
end
end
end
18 changes: 18 additions & 0 deletions lib/cove/deployment_config/deployable_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Cove
class DeploymentConfig
class DeployableFile
def initialize(base_path, file)
@base_path = base_path
@file = file
end

def content
@file.content
end

def host_path
File.join(@base_path, @file.path)
end
end
end
end
15 changes: 15 additions & 0 deletions lib/cove/deployment_config/digest.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Cove
class DeploymentConfig
class Digest
def for(digestables)
to_digest = digestables.sort_by do |digestable|
[digestable.first, digestable.second]
end.map do |digestable|
digestable.join("")
end.join("")

::Digest::SHA2.hexdigest(to_digest)
end
end
end
end
33 changes: 33 additions & 0 deletions lib/cove/deployment_config/entry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Cove
class DeploymentConfig
class Entry
attr_reader :name, :target, :source

def initialize(registry:, deployment:, name:, source:, target:)
@registry = registry
@deployment = deployment
@name = name
@source = source
@target = target
end

def digestables
files.map do |file|
[name, file.path, file.content]
end
end

def directories
files.map(&:directory).compact.uniq
end

def files
@files ||= resolver.call(registry: @registry, deployment: @deployment, source: source)
end

def resolver
@resolver ||= FileResolver.new
end
end
end
end
32 changes: 32 additions & 0 deletions lib/cove/deployment_config/file_resolver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module Cove
class DeploymentConfig
class FileResolver
class FileNotFound < Cove::Error
def initialize(path)
super("File not found: #{path}")
end
end

def call(registry:, deployment:, source:)
path = File.join(deployment.directory, source)
renderer = Renderer.new(registry, deployment)

raise FileNotFound.new(path) unless File.exist?(path)

if File.directory?(path)
Dir.glob(File.join(path, "**/*")).select do |file|
File.file?(file)
end.map do |file|
[file.gsub(/^#{path}\//, ""), renderer.call(File.read(file))]
end
else
[
[File.basename(path), renderer.call(File.read(path))]
]
end.map do |path, content|
ConfigFile.new(path: path, content: content)
end
end
end
end
end
50 changes: 50 additions & 0 deletions lib/cove/deployment_config/renderer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module Cove
class DeploymentConfig
class Renderer
attr_reader :registry, :deployment

def initialize(registry, deployment)
@registry = registry
@deployment = deployment
end

def call(template)
ERB.new(template, trim_mode: "-").result(binding)
end

def host_running(query)
HostsQuery.new(@registry).resolve(query).first
end

def hosts_running(query)
HostsQuery.new(@registry).resolve(query)
end

delegate :service_name, :role_name, :version, to: :deployment

class HostsQuery
attr_reader :registry

def initialize(registry)
@registry = registry
end

def resolve(query)
query.match?("/") ? hosts_for_role(query) : hosts_for_service(query)
end

private

def hosts_for_service(service_name)
service = registry.services[service_name]
registry.hosts_for_service(service)
end

def hosts_for_role(role_name)
role = registry.roles[role_name]
registry.hosts_for_roles(role)
end
end
end
end
end
78 changes: 0 additions & 78 deletions lib/cove/deployment_config_assembly.rb

This file was deleted.

Loading