mirror of
https://github.com/apple/container.git
synced 2026-07-15 14:07:04 +00:00
0fd7005119
Fixes a warning missed in https://github.com/apple/container/pull/516.
208 lines
8.0 KiB
Swift
208 lines
8.0 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import ContainerBuildIR
|
|
|
|
protocol InstructionVisitor {
|
|
func visit(_ from: FromInstruction) throws
|
|
func visit(_ run: RunInstruction) throws
|
|
func visit(_ copy: CopyInstruction) throws
|
|
func visit(_ cmd: CMDInstruction) throws
|
|
func visit(_ label: LabelInstruction) throws
|
|
func visit(_ expose: ExposeInstruction) throws
|
|
func visit(_ arg: ArgInstruction) throws
|
|
// TODO: These instructions have to perform the ARG substitution checked in `testSimpleDockerfileArgInInstructions()`:
|
|
// - ADD
|
|
// - ENV
|
|
// - STOPSIGNAL
|
|
// - USER
|
|
// - VOLUME
|
|
// - WORKDIR
|
|
// - ONBUILD
|
|
// - ENTRYPOINT
|
|
}
|
|
|
|
/// DockerInstructionVisitor visits each provided DockerInstruction and builds a
|
|
/// build graph from the instructions.
|
|
public class DockerInstructionVisitor: InstructionVisitor {
|
|
|
|
internal let graphBuilder: GraphBuilder
|
|
|
|
init() {
|
|
self.graphBuilder = GraphBuilder()
|
|
}
|
|
|
|
func buildGraph(from: [any DockerInstruction]) throws -> BuildGraph {
|
|
for instruction in from {
|
|
try instruction.accept(self)
|
|
}
|
|
return try graphBuilder.build()
|
|
}
|
|
}
|
|
|
|
extension DockerInstructionVisitor {
|
|
private func substituteArgsInCommand(_ command: Command) -> Command {
|
|
switch command {
|
|
case .shell(let cmd):
|
|
let substitutedCmd = graphBuilder.substituteArgs(cmd, inFromContext: false)
|
|
return .shell(substitutedCmd)
|
|
case .exec(let args):
|
|
let substitutedArgs = args.map { graphBuilder.substituteArgs($0, inFromContext: false) }
|
|
return .exec(substitutedArgs)
|
|
}
|
|
}
|
|
|
|
func visit(_ from: FromInstruction) throws {
|
|
let imageString = graphBuilder.substituteArgs(from.image, inFromContext: true)
|
|
|
|
guard let imageRef = ImageReference(parsing: imageString) else {
|
|
throw ParseError.invalidImage(imageString)
|
|
}
|
|
|
|
if let stageName = from.stageName {
|
|
try graphBuilder.stage(name: stageName, from: imageRef, platform: from.platform)
|
|
} else {
|
|
try graphBuilder.stage(from: imageRef, platform: from.platform)
|
|
}
|
|
}
|
|
|
|
func visit(_ run: RunInstruction) throws {
|
|
var mounts: [Mount] = []
|
|
for m in run.mounts {
|
|
guard let type = m.type else {
|
|
throw ParseError.unexpectedValue
|
|
}
|
|
|
|
let mountSource: MountSource?
|
|
switch m.type {
|
|
case .bind, .cache:
|
|
guard let source = m.source else {
|
|
throw ParseError.missingRequiredField(MountOptionNames.source.rawValue)
|
|
}
|
|
if let from = m.from, from != "" {
|
|
if let _ = graphBuilder.getStage(stageName: from) {
|
|
mountSource = .stage(.named(from), path: source)
|
|
} else if let context = graphBuilder.resolveArg(key: from, inFromContext: false) {
|
|
mountSource = .context(context, path: source)
|
|
} else {
|
|
// mount source is an image name
|
|
guard let imageRef = ImageReference(parsing: from) else {
|
|
throw ParseError.invalidImage(from)
|
|
}
|
|
mountSource = .image(imageRef, path: source)
|
|
}
|
|
} else {
|
|
// from was not set or is empty, default is local source
|
|
mountSource = .local(source)
|
|
}
|
|
case .secret:
|
|
mountSource = .secret(m.id!)
|
|
case .ssh:
|
|
mountSource = .sshAgent
|
|
default:
|
|
// this covers .tmpfs case as well
|
|
mountSource = nil
|
|
}
|
|
|
|
guard let options = m.options else {
|
|
throw ParseError.unexpectedValue
|
|
}
|
|
|
|
guard let readonly = options.readonly else {
|
|
throw ParseError.unexpectedValue
|
|
}
|
|
|
|
let mountOptions = MountOptions(
|
|
readOnly: readonly,
|
|
uid: options.uid,
|
|
gid: options.gid,
|
|
mode: options.mode,
|
|
size: options.size,
|
|
sharing: options.sharing,
|
|
required: options.required)
|
|
|
|
let graphMount = Mount(
|
|
type: type,
|
|
target: m.target,
|
|
envTarget: m.env,
|
|
source: mountSource,
|
|
options: mountOptions)
|
|
|
|
mounts.append(graphMount)
|
|
}
|
|
|
|
let substitutedCommand = substituteArgsInCommand(run.command)
|
|
try graphBuilder.runWithCmd(substitutedCommand, mounts: mounts)
|
|
}
|
|
|
|
func visit(_ copy: CopyInstruction) throws {
|
|
// TODO katiewasnothere: plumb through "--link" option
|
|
|
|
let substitutedSources = copy.sources.map { graphBuilder.substituteArgs($0, inFromContext: false) }
|
|
let substitutedDestination = graphBuilder.substituteArgs(copy.destination, inFromContext: false)
|
|
|
|
if let from = copy.from {
|
|
var source: FilesystemSource
|
|
if let _ = graphBuilder.getStage(stageName: from) {
|
|
source = .stage(.named(from), paths: substitutedSources)
|
|
} else if let context = graphBuilder.resolveArg(key: from, inFromContext: false) {
|
|
source = .context(ContextSource(name: context, paths: substitutedSources))
|
|
} else {
|
|
guard let imageRef = ImageReference(parsing: from) else {
|
|
throw ParseError.invalidImage(from)
|
|
}
|
|
source = .image(imageRef, paths: substitutedSources)
|
|
}
|
|
try graphBuilder.copy(from: source, to: substitutedDestination, chown: copy.chown, chmod: copy.chmod)
|
|
return
|
|
}
|
|
try graphBuilder.copyFromContext(paths: substitutedSources, to: substitutedDestination, chown: copy.chown, chmod: copy.chmod)
|
|
}
|
|
|
|
func visit(_ cmd: CMDInstruction) throws {
|
|
let substitutedCommand = substituteArgsInCommand(cmd.command)
|
|
try graphBuilder.cmd(substitutedCommand)
|
|
}
|
|
|
|
func visit(_ label: LabelInstruction) throws {
|
|
let substitutedLabels = label.labels.mapValues { graphBuilder.substituteArgs($0, inFromContext: false) }
|
|
try graphBuilder.labelBatch(labels: substitutedLabels)
|
|
}
|
|
|
|
func visit(_ expose: ExposeInstruction) throws {
|
|
let substitutedPortStrings = expose.ports.map { graphBuilder.substituteArgs($0, inFromContext: false) }
|
|
let substitutedPorts = try substitutedPortStrings.map(parsePort)
|
|
try graphBuilder.expose(substitutedPorts)
|
|
}
|
|
|
|
func visit(_ arg: ArgInstruction) throws {
|
|
for argDef in arg.args {
|
|
let substitutedDefaultValue: String?
|
|
if let defaultValue = argDef.defaultValue {
|
|
substitutedDefaultValue = graphBuilder.substituteArgs(defaultValue, inFromContext: !graphBuilder.hasActiveStage)
|
|
} else {
|
|
substitutedDefaultValue = nil
|
|
}
|
|
|
|
if graphBuilder.hasActiveStage {
|
|
try graphBuilder.arg(argDef.name, defaultValue: substitutedDefaultValue)
|
|
} else {
|
|
graphBuilder.fromOnlyArg(argDef.name, defaultValue: substitutedDefaultValue)
|
|
}
|
|
}
|
|
}
|
|
}
|