Class: Kamal::Dev::ComposeParser
- Inherits:
-
Object
- Object
- Kamal::Dev::ComposeParser
- Defined in:
- lib/kamal/dev/compose_parser.rb
Overview
Parser for Docker Compose files
Parses compose.yaml files to extract service definitions, build contexts, and Dockerfiles. Identifies main application service vs dependent services (databases, caches, etc.) for deployment orchestration.
Instance Attribute Summary collapse
-
#compose_data ⇒ Object
readonly
Returns the value of attribute compose_data.
-
#compose_file_path ⇒ Object
readonly
Returns the value of attribute compose_file_path.
Instance Method Summary collapse
-
#dependent_services ⇒ Array<String>
Get dependent services (services without build sections).
-
#has_build_section?(service_name) ⇒ Boolean
Check if service has a build section.
-
#initialize(compose_file_path) ⇒ ComposeParser
constructor
Initialize parser with compose file path.
-
#main_service ⇒ String?
Identify the main application service.
-
#service_build_context(service_name) ⇒ String
Get build context for a service.
-
#service_dockerfile(service_name) ⇒ String
Get Dockerfile path for a service.
-
#services ⇒ Hash
Get all services from compose file.
-
#transform_for_deployment(image_ref, config: nil) ⇒ String
Transform compose file for deployment.
Constructor Details
#initialize(compose_file_path) ⇒ ComposeParser
Initialize parser with compose file path
33 34 35 36 |
# File 'lib/kamal/dev/compose_parser.rb', line 33 def initialize(compose_file_path) @compose_file_path = compose_file_path @compose_data = load_and_parse end |
Instance Attribute Details
#compose_data ⇒ Object (readonly)
Returns the value of attribute compose_data.
27 28 29 |
# File 'lib/kamal/dev/compose_parser.rb', line 27 def compose_data @compose_data end |
#compose_file_path ⇒ Object (readonly)
Returns the value of attribute compose_file_path.
27 28 29 |
# File 'lib/kamal/dev/compose_parser.rb', line 27 def compose_file_path @compose_file_path end |
Instance Method Details
#dependent_services ⇒ Array<String>
Get dependent services (services without build sections)
These are typically databases, caches, message queues, etc. that use pre-built images from registries
127 128 129 |
# File 'lib/kamal/dev/compose_parser.rb', line 127 def dependent_services services.select { |_, config| !config.key?("build") }.keys end |
#has_build_section?(service_name) ⇒ Boolean
Check if service has a build section
114 115 116 117 118 119 |
# File 'lib/kamal/dev/compose_parser.rb', line 114 def has_build_section?(service_name) service = services[service_name] return false unless service service.key?("build") end |
#main_service ⇒ String?
Identify the main application service
Uses heuristic: first service with a build: section, or first service if none have build sections
51 52 53 54 55 56 57 58 |
# File 'lib/kamal/dev/compose_parser.rb', line 51 def main_service # Find first service with build section service_with_build = services.find { |_, config| config.key?("build") } return service_with_build[0] if service_with_build # Fallback to first service services.keys.first end |
#service_build_context(service_name) ⇒ String
Get build context for a service
Resolves context path relative to the compose file's directory, since Docker Compose interprets paths relative to the compose file location.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/kamal/dev/compose_parser.rb', line 67 def service_build_context(service_name) service = services[service_name] return "." unless service build_config = service["build"] return "." unless build_config # Get context from build config context = if build_config.is_a?(String) # Handle string build path (shorthand) - this is the context build_config else # Handle object build config build_config["context"] || "." end # Resolve context relative to compose file's directory # Docker Compose does this automatically, but we're extracting values compose_dir = File.dirname(compose_file_path) File.(context, compose_dir) end |
#service_dockerfile(service_name) ⇒ String
Get Dockerfile path for a service
Returns path relative to the build context (as Docker expects), NOT resolved to absolute path.
96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/kamal/dev/compose_parser.rb', line 96 def service_dockerfile(service_name) service = services[service_name] return "Dockerfile" unless service build_config = service["build"] return "Dockerfile" unless build_config # Handle object build config return "Dockerfile" if build_config.is_a?(String) # Return dockerfile path as-is (relative to build context) build_config["dockerfile"] || "Dockerfile" end |
#services ⇒ Hash
Get all services from compose file
41 42 43 |
# File 'lib/kamal/dev/compose_parser.rb', line 41 def services compose_data.fetch("services", {}) end |
#transform_for_deployment(image_ref, config: nil) ⇒ String
Transform compose file for deployment
Replaces build: sections with image: references pointing to the pushed registry image. Removes local bind mounts (DevPod-style). Optionally injects git clone functionality for remote deployments. Preserves named volumes and other service properties.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/kamal/dev/compose_parser.rb', line 142 def transform_for_deployment(image_ref, config: nil) transformed = deep_copy(compose_data) main = main_service if main && transformed["services"][main] # Remove build section transformed["services"][main].delete("build") # Add image reference transformed["services"][main]["image"] = image_ref # Remove local bind mounts (DevPod-style: code will be cloned, not mounted) # Keep named volumes (databases, caches, etc.) if transformed["services"][main]["volumes"] transformed["services"][main]["volumes"] = transformed["services"][main]["volumes"].reject do |volume| # Reject if it's a bind mount (contains ":" and first part is a path) if volume.is_a?(String) && volume.include?(":") source, _target = volume.split(":", 2) # Named volumes don't start with . or / or ~ source.start_with?(".", "/", "~") else false end end # Remove volumes array if empty transformed["services"][main].delete("volumes") if transformed["services"][main]["volumes"].empty? end # Inject git clone environment variables if configured # The actual cloning is handled by the entrypoint script in the image if config&.git_clone_enabled? inject_git_env_vars!(transformed["services"][main], config) end end # Convert back to YAML YAML.dump(transformed) rescue => e raise Kamal::Dev::ConfigurationError, "Failed to transform compose file: #{e.message}" end |