Extract unpacks the feature from the image and returns a set of lines that should be appended to a Dockerfile to install the feature.
(featureRef, featureName, featureDir, containerUser, remoteUser string, useBuildContexts bool, options map[string]any)
| 193 | // Extract unpacks the feature from the image and returns a set of lines |
| 194 | // that should be appended to a Dockerfile to install the feature. |
| 195 | func (s *Spec) Compile(featureRef, featureName, featureDir, containerUser, remoteUser string, useBuildContexts bool, options map[string]any) (string, string, error) { |
| 196 | // TODO not sure how we figure out _(REMOTE|CONTAINER)_USER_HOME |
| 197 | // as per the feature spec. |
| 198 | // See https://containers.dev/implementors/features/#user-env-var |
| 199 | var fromDirective string |
| 200 | runDirective := []string{ |
| 201 | "_CONTAINER_USER=" + strconv.Quote(containerUser), |
| 202 | "_REMOTE_USER=" + strconv.Quote(remoteUser), |
| 203 | } |
| 204 | for key, value := range s.Options { |
| 205 | strValue := fmt.Sprint(value.Default) |
| 206 | provided, ok := options[key] |
| 207 | if ok { |
| 208 | strValue = fmt.Sprint(provided) |
| 209 | // delete so we can check if there are any unknown options |
| 210 | delete(options, key) |
| 211 | } |
| 212 | runDirective = append(runDirective, fmt.Sprintf(`%s=%q`, convertOptionNameToEnv(key), strValue)) |
| 213 | } |
| 214 | if len(options) > 0 { |
| 215 | return "", "", fmt.Errorf("unknown option: %v", options) |
| 216 | } |
| 217 | // It's critical that the Dockerfile produced is deterministic, |
| 218 | // regardless of map iteration order. |
| 219 | sort.Strings(runDirective) |
| 220 | // See https://containers.dev/implementors/features/#invoking-installsh |
| 221 | if useBuildContexts { |
| 222 | // Use a deterministic target directory to make the resulting Dockerfile cacheable |
| 223 | featureDir = "/.envbuilder/features/" + featureName |
| 224 | fromDirective = "FROM scratch AS envbuilder_feature_" + featureName + "\nCOPY --from=" + featureRef + " / /\n" |
| 225 | runDirective = append([]string{"RUN", "--mount=type=bind,from=envbuilder_feature_" + featureName + ",target=" + featureDir + ",rw"}, runDirective...) |
| 226 | } else { |
| 227 | runDirective = append([]string{"RUN"}, runDirective...) |
| 228 | } |
| 229 | runDirective = append(runDirective, "./install.sh") |
| 230 | |
| 231 | comment := "" |
| 232 | if s.Name != "" { |
| 233 | comment += "# " + s.Name |
| 234 | } |
| 235 | if s.Version != "" { |
| 236 | comment += " " + s.Version |
| 237 | } |
| 238 | if s.Description != "" { |
| 239 | comment += " - " + s.Description |
| 240 | } |
| 241 | lines := []string{} |
| 242 | if comment != "" { |
| 243 | lines = append(lines, comment) |
| 244 | } |
| 245 | lines = append(lines, "WORKDIR "+featureDir) |
| 246 | envKeys := make([]string, 0, len(s.ContainerEnv)) |
| 247 | for key := range s.ContainerEnv { |
| 248 | envKeys = append(envKeys, key) |
| 249 | } |
| 250 | // It's critical that the Dockerfile produced is deterministic, |
| 251 | // regardless of map iteration order. |
| 252 | sort.Strings(envKeys) |