From bd36635045f78b727085278ae29f35569a10a0a8 Mon Sep 17 00:00:00 2001 From: Thomas DA ROCHA Date: Wed, 25 Sep 2024 17:33:25 +0200 Subject: [PATCH] fix: Cache struct from field and add struct documentation (#282) * fix: cache from struct * docs: Document the Dofigen struct * style: Format * fix: Builder dependencies and locks * style: Format --- docs/struct.md | 228 ++++++++++++++++++++++++++++++++++++++++++ dofigen.yml | 2 +- src/dofigen_struct.rs | 87 +++++++++++++++- src/from_str.rs | 15 ++- src/generator.rs | 30 +++++- src/lock.rs | 53 ++++++++++ 6 files changed, 405 insertions(+), 10 deletions(-) create mode 100644 docs/struct.md diff --git a/docs/struct.md b/docs/struct.md new file mode 100644 index 0000000..1c1ed64 --- /dev/null +++ b/docs/struct.md @@ -0,0 +1,228 @@ +# Dofigen struct reference + +This is the reference for the Dofigen configuration file structure. + +The Dofigen struct is a YAML or JSON object that can be used to generate a Dockerfile. + +The struct is permissive in order to make it easy to write and read. +For example, some objects can be parsed from string and all arrays can be parsed from single element. + +- [Dofigen struct reference](#dofigen-struct-reference) + - [Dofigen](#dofigen) + - [Extend](#extend) + - [Stage](#stage) + - [FromContext](#fromcontext) + - [User](#user) + - [CopyResource](#copyresource) + - [Run](#run) + - [Cache](#cache) + - [Bind](#bind) + - [Healthcheck](#healthcheck) + - [ImageName](#imagename) + - [Copy](#copy) + - [AddGitRepo](#addgitrepo) + - [Add](#add) + - [CopyOptions](#copyoptions) + - [Port](#port) + +## Dofigen + +This is the root object of the Dofigen configuration file. + +It extends the [Extend](#extend) and [Stage](#stage) structures. + +| Field | Type | Description | +| --- | --- | --- | +| `context` | string[] | The context of the Docker build. This is used to generate a `.dockerignore` file. | +| `ignore` | string[] | The elements to ignore from the build context. This is used to generate a `.dockerignore` file. | +| `builders` | map | The builder stages of the Dockerfile. | +| `entrypoint` | string[] | The entrypoint of the Dockerfile. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#entrypoint). | +| `cmd` | string[] | The default command of the Dockerfile. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#cmd). | +| `expose` | [Port](#port)[] | The ports exposed by the Dockerfile. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#expose). | +| `healthcheck` | [Healthcheck](#healthcheck) | The healthcheck of the Dockerfile. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#healthcheck). | + +## Extend + +This let you extend a struct from local or remote files. + +| Field | Type | Description | +| --- | --- | --- | +| `extend` | string or string[] | The files to extend. | + +## Stage + +This represents a Dockerfile stage. + +It extends the [Run](#run) structure. + +| Field | Type | Description | +| --- | --- | --- | +| `from...` | [FromContext](#fromcontext) | The base of the stage. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#from). | +| `user` | [User](#user) | The user and group of the stage. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#user). | +| `workdir` | string | The working directory of the stage. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#workdir). | +| `arg` | map | The build args that can be used in the stage. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#arg). | +| `env` | map | The environment variables of the stage. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#env). | +| `copy` | [CopyResource](#copyresource) or [CopyResource](#copyresource)[] | The copy instructions of the stage. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#copy) and [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#add). | +| `root` | [Run](#run) | The run instructions of the stage as root user. | + +## FromContext + +This represents a context origin. + +Possible fields are: + +- `fromImage` ([ImageName](#imagename)) : A Docker image. +- `fromBuilder` (string) : A builder from the same Dofigen file. +- `fromContext`: (string) : A Docker build context. See https://docs.docker.com/reference/cli/docker/buildx/build/#build-context + +## User + +This represents user and group definition. + +It can be parsed from string. + +| Field | Type | Description | +| --- | --- | --- | +| `user` | string | The user name or ID. | +| `group` | string | The group name or ID. | + +## CopyResource + +This represents the COPY/ADD instructions in a Dockerfile. + +It can be one of the following objects: + +- [Copy](#copy) : A copy instruction. +- [Add](#add) : An add instruction. +- [AddGitRepo](#addgitrepo) : An add instruction from a git repository. + +## Run + +This represents a run command. + +| Field | Type | Description | +| --- | --- | --- | +| `run` | string or string[] | The commands to run. | +| `cache` | [Cache](#cache)[] | The cache definitions during the run. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#run---mounttypecache). | +| `bind` | [Bind](#bind)[] | The file system bindings during the run. See [Dockerfile reference](https://docs.docker.com/reference/dockerfile/#run---mounttypebind). | + +## Cache + +This represents a cache definition during a run. + +It can be parsed from string. + +| Field | Type | Description | +| --- | --- | --- | +| `id` | string | The id of the cache. This is used to share the cache between different stages. | +| `target` | string | The target path of the cache. | +| `readonly` | boolean | Defines if the cache is readonly. | +| `sharing` | "shared" or "private" or "locked" | The sharing strategy of the cache. | +| `from...` | [FromContext](#fromcontext) | The base of the cache mount. | +| `source` | string | Subpath in the from to mount. | +| `chmod` | string | The permissions of the cache. | +| `chown` | [User](#user) | The user and group that own the cache. | + +## Bind + +This represents file system binding during a run. + +It can be parsed from string. + +| Field | Type | Description | +| --- | --- | --- | +| `target` | string | The target path of the bind. | +| `from...` | [FromContext](#fromcontext) | The base of the cache mount. | +| `source` | string | Subpath in the from to mount. | +| `readwrite` | boolean | Defines if the bind is read and write. | + + +## Healthcheck + +This represents the Dockerfile healthcheck instruction. + +| Field | Type | Description | +| --- | --- | --- | +| `cmd` | string | The test command to run. | +| `interval` | string | The time between running the check (ms|s|m|h). | +| `timeout` | string | The time to wait before considering the check to have hung (ms|s|m|h). | +| `startPeriod` | string | The time to wait for the container to start before starting health-retries countdown (ms|s|m|h). | +| `retries` | int | The number of consecutive failures needed to consider a container as unhealthy. | + +## ImageName + +This represents a Docker image name. + +It can be parsed from string. + +| Field | Type | Description | +| --- | --- | --- | +| `host` | string | The host of the image registry. | +| `port` | int | The port of the image registry. | +| `path` | string | The path of the image repository. | + +The version of the image can also be set with one of the following fields: + +- `tag` : The tag of the image. +- `digest` : The digest of the image. + +## Copy + +This represents the COPY instruction in a Dockerfile. + +It extends the [CopyOptions](#copyoptions) structure. + +Can be parsed from string. + +| Field | Type | Description | +| --- | --- | --- | +| `from...` | [FromContext](#fromcontext) | The origin of the copy. See https://docs.docker.com/reference/dockerfile/#copy---from | +| `paths` | string[] | The paths to copy. | + +## AddGitRepo + +This represents the ADD instruction in a Dockerfile specific for Git repositories. + +It extends the [CopyOptions](#copyoptions) structure. + +Can be parsed from string. + +| Field | Type | Description | +| --- | --- | --- | +| `repo` | string | The URL of the Git repository. | +| `keepGitDir` | boolean | Keep the git directory. See https://docs.docker.com/reference/dockerfile/#add---keep-git-dir | + +## Add + +This represents the ADD instruction in a Dockerfile for files from URLs or to uncompress an archive. + +It extends the [CopyOptions](#copyoptions) structure. + +Can be parsed from string. + +| Field | Type | Description | +| --- | --- | --- | +| `files` | string[] | The source files to add. | +| `checksum` | string | The checksum of the files. See https://docs.docker.com/reference/dockerfile/#add---checksum | + +## CopyOptions + +This represents the options of a COPY/ADD instructions. + +| Field | Type | Description | +| --- | --- | --- | +| `target` | string | The target path of the copied files. | +| `chown` | [User](#user) | The user and group that own the copied files. See https://docs.docker.com/reference/dockerfile/#copy---chown---chmod | +| `chmod` | string | The permissions of the copied files. See https://docs.docker.com/reference/dockerfile/#copy---chown---chmod | +| `link` | boolean | Use of the link flag. See https://docs.docker.com/reference/dockerfile/#copy---link | + +## Port + +This represents a port definition. + +It can be parsed from string. + +| Field | Type | Description | +| --- | --- | --- | +| `port` | int | The port number. | +| `protocol` | "tcp" or "udp" | The protocol of the port. | diff --git a/dofigen.yml b/dofigen.yml index 26f0b59..1783d9f 100644 --- a/dofigen.yml +++ b/dofigen.yml @@ -5,7 +5,7 @@ arg: copy: - paths: builds/${TARGETPLATFORM}/dofigen target: /bin/ -entrypoint: dofigen +entrypoint: /bin/dofigen cmd: --help context: - "/builds" diff --git a/src/dofigen_struct.rs b/src/dofigen_struct.rs index be2f3da..b305ea0 100644 --- a/src/dofigen_struct.rs +++ b/src/dofigen_struct.rs @@ -22,31 +22,43 @@ use url::Url; ) )] pub struct Dofigen { + /// The context of the Docker build + /// This is used to generate a .dockerignore file #[patch(name = "VecPatch")] #[serde(skip_serializing_if = "Vec::is_empty")] pub context: Vec, + /// The elements to ignore from the build context + /// This is used to generate a .dockerignore file #[patch(name = "VecPatch")] #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ignores"))))] #[serde(skip_serializing_if = "Vec::is_empty")] pub ignore: Vec, + /// The builder stages of the Dockerfile #[patch(name = "HashMapDeepPatch")] #[serde(skip_serializing_if = "HashMap::is_empty")] pub builders: HashMap, + /// The runtime stage of the Dockerfile #[patch(name = "StagePatch", attribute(serde(flatten)))] #[serde(flatten)] pub stage: Stage, + /// The entrypoint of the Dockerfile + /// See https://docs.docker.com/reference/dockerfile/#entrypoint #[patch(name = "VecPatch")] #[serde(skip_serializing_if = "Vec::is_empty")] pub entrypoint: Vec, + /// The default command of the Dockerfile + /// See https://docs.docker.com/reference/dockerfile/#cmd #[patch(name = "VecPatch")] #[serde(skip_serializing_if = "Vec::is_empty")] pub cmd: Vec, + /// The ports exposed by the Dockerfile + /// See https://docs.docker.com/reference/dockerfile/#expose #[cfg_attr( feature = "permissive", patch(name = "VecDeepPatch>") @@ -62,6 +74,8 @@ pub struct Dofigen { #[serde(skip_serializing_if = "Vec::is_empty")] pub expose: Vec, + /// The healthcheck of the Dockerfile + /// See https://docs.docker.com/reference/dockerfile/#healthcheck #[patch(name = "Option")] #[serde(skip_serializing_if = "Option::is_none")] pub healthcheck: Option, @@ -82,10 +96,14 @@ pub struct Dofigen { ) )] pub struct Stage { + /// The base of the stage + /// See https://docs.docker.com/reference/dockerfile/#from #[serde(flatten, skip_serializing_if = "FromContext::is_empty")] #[patch(name = "FromContextPatch", attribute(serde(flatten, default)))] pub from: FromContext, + /// The user and group of the stage + /// See https://docs.docker.com/reference/dockerfile/#user #[cfg_attr( feature = "permissive", patch(name = "Option>") @@ -94,19 +112,27 @@ pub struct Stage { #[serde(skip_serializing_if = "Option::is_none")] pub user: Option, + /// The working directory of the stage + /// See https://docs.docker.com/reference/dockerfile/#workdir #[serde(skip_serializing_if = "Option::is_none")] pub workdir: Option, + /// The build args that can be used in the stage + /// See https://docs.docker.com/reference/dockerfile/#arg #[patch(name = "HashMapPatch")] #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "args"))))] #[serde(skip_serializing_if = "HashMap::is_empty")] pub arg: HashMap, + /// The environment variables of the stage + /// See https://docs.docker.com/reference/dockerfile/#env #[patch(name = "HashMapPatch")] #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "envs"))))] #[serde(skip_serializing_if = "HashMap::is_empty")] pub env: HashMap, + /// The copy instructions of the stage + /// See https://docs.docker.com/reference/dockerfile/#copy and https://docs.docker.com/reference/dockerfile/#add #[cfg_attr( not(feature = "strict"), patch(attribute(serde( @@ -127,16 +153,19 @@ pub struct Stage { #[serde(skip_serializing_if = "Vec::is_empty")] pub copy: Vec, + /// The run instructions of the stage as root user #[patch(name = "Option")] #[serde(skip_serializing_if = "Option::is_none")] pub root: Option, + /// The run instructions of the stage + /// See https://docs.docker.com/reference/dockerfile/#run #[patch(name = "RunPatch", attribute(serde(flatten)))] #[serde(flatten)] pub run: Run, } -/// Represents a run executed as root +/// Represents a run command #[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)] #[patch( attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)), @@ -151,11 +180,14 @@ pub struct Stage { ) )] pub struct Run { + /// The commands to run #[patch(name = "VecPatch")] #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "script"))))] #[serde(skip_serializing_if = "Vec::is_empty")] pub run: Vec, + /// The cache definitions during the run + /// See https://docs.docker.com/reference/dockerfile/#run---mounttypecache #[cfg_attr( feature = "permissive", patch(name = "VecDeepPatch>") @@ -168,6 +200,9 @@ pub struct Run { #[serde(skip_serializing_if = "Vec::is_empty")] pub cache: Vec, + /// The file system bindings during the run + /// This is used to mount a file or directory from the host into the container only during the run and it's faster than a copy + /// See https://docs.docker.com/reference/dockerfile/#run---mounttypebind #[cfg_attr( feature = "permissive", patch(name = "VecDeepPatch>") @@ -196,28 +231,37 @@ pub struct Run { ) )] pub struct Cache { + /// The id of the cache + /// This is used to share the cache between different stages #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, + /// The target path of the cache pub target: String, + /// Defines if the cache is readonly #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ro"))))] #[serde(skip_serializing_if = "Option::is_none")] pub readonly: Option, + /// The sharing strategy of the cache #[serde(skip_serializing_if = "Option::is_none")] pub sharing: Option, - /// - #[serde(skip_serializing_if = "Option::is_none")] - pub from: Option, + /// The base of the cache mount + #[serde(flatten, skip_serializing_if = "FromContext::is_empty")] + #[patch(name = "FromContextPatch", attribute(serde(flatten)))] + pub from: FromContext, + /// Subpath in the from to mount #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, + /// The permissions of the cache #[serde(skip_serializing_if = "Option::is_none")] pub chmod: Option, + /// The user and group that own the cache #[patch(name = "Option")] #[serde(skip_serializing_if = "Option::is_none")] pub chown: Option, @@ -238,21 +282,26 @@ pub struct Cache { ) )] pub struct Bind { + /// The target path of the bind pub target: String, + /// The base of the bind #[serde(flatten, skip_serializing_if = "FromContext::is_empty")] #[patch(name = "FromContextPatch", attribute(serde(flatten)))] pub from: FromContext, + /// Subpath in the from to mount #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, + /// Defines if the bind is read and write #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "rw"))))] #[serde(skip_serializing_if = "Option::is_none")] pub readwrite: Option, } /// Represents the Dockerfile healthcheck instruction +/// See https://docs.docker.com/reference/dockerfile/#healthcheck #[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)] #[patch( attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)), @@ -266,17 +315,22 @@ pub struct Bind { ) )] pub struct Healthcheck { + /// The test to run pub cmd: String, + /// The interval between two tests #[serde(skip_serializing_if = "Option::is_none")] pub interval: Option, + /// The timeout of the test #[serde(skip_serializing_if = "Option::is_none")] pub timeout: Option, + /// The start period of the test #[serde(skip_serializing_if = "Option::is_none")] pub start: Option, + /// The number of retries #[serde(skip_serializing_if = "Option::is_none")] pub retries: Option, } @@ -295,14 +349,18 @@ pub struct Healthcheck { ) )] pub struct ImageName { + /// The host of the image registry #[serde(skip_serializing_if = "Option::is_none")] pub host: Option, + /// The port of the image registry #[serde(skip_serializing_if = "Option::is_none")] pub port: Option, + /// The path of the image repository pub path: String, + /// The version of the image #[serde(flatten, skip_serializing_if = "Option::is_none")] #[patch(attribute(serde(flatten)))] pub version: Option, @@ -323,11 +381,13 @@ pub struct ImageName { ) )] pub struct Copy { + /// The origin of the copy /// See https://docs.docker.com/reference/dockerfile/#copy---from #[serde(flatten, skip_serializing_if = "FromContext::is_empty")] #[patch(name = "FromContextPatch", attribute(serde(flatten)))] pub from: FromContext, + /// The paths to copy #[patch(name = "VecPatch")] #[cfg_attr( not(feature = "strict"), @@ -336,6 +396,7 @@ pub struct Copy { #[serde(skip_serializing_if = "Vec::is_empty")] pub paths: Vec, + /// The options of the copy #[serde(flatten)] #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))] pub options: CopyOptions, @@ -366,8 +427,10 @@ pub struct Copy { ) )] pub struct AddGitRepo { + /// The URL of the Git repository pub repo: String, + /// The options of the copy #[serde(flatten)] #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))] pub options: CopyOptions, @@ -377,6 +440,7 @@ pub struct AddGitRepo { // #[patch(name = "VecPatch")] // #[serde(skip_serializing_if = "Vec::is_empty")] // pub exclude: Vec, + /// Keep the git directory /// See https://docs.docker.com/reference/dockerfile/#add---keep-git-dir #[serde(skip_serializing_if = "Option::is_none")] pub keep_git_dir: Option, @@ -396,21 +460,24 @@ pub struct AddGitRepo { ) )] pub struct Add { + /// The files to add #[patch(name = "VecPatch")] #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "file"))))] #[serde(skip_serializing_if = "Vec::is_empty")] pub files: Vec, + /// The options of the copy #[serde(flatten)] #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))] pub options: CopyOptions, + /// The checksum of the files /// See https://docs.docker.com/reference/dockerfile/#add---checksum #[serde(skip_serializing_if = "Option::is_none")] pub checksum: Option, } -/// Represents the ADD instruction in a Dockerfile file from URLs or uncompress an archive. +/// Represents the options of a COPY/ADD instructions #[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)] #[patch( attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)), @@ -424,6 +491,7 @@ pub struct Add { ) )] pub struct CopyOptions { + /// The target path of the copied files #[cfg_attr( not(feature = "strict"), patch(attribute(serde(alias = "destination"))) @@ -431,15 +499,18 @@ pub struct CopyOptions { #[serde(skip_serializing_if = "Option::is_none")] pub target: Option, + /// The user and group that own the copied files /// See https://docs.docker.com/reference/dockerfile/#copy---chown---chmod #[patch(name = "Option")] #[serde(skip_serializing_if = "Option::is_none")] pub chown: Option, + /// The permissions of the copied files /// See https://docs.docker.com/reference/dockerfile/#copy---chown---chmod #[serde(skip_serializing_if = "Option::is_none")] pub chmod: Option, + /// Use of the link flag /// See https://docs.docker.com/reference/dockerfile/#copy---link #[serde(skip_serializing_if = "Option::is_none")] pub link: Option, @@ -459,8 +530,12 @@ pub struct CopyOptions { ) )] pub struct User { + /// The user name or ID + /// The ID is preferred pub user: String, + /// The group name or ID + /// The ID is preferred #[serde(skip_serializing_if = "Option::is_none")] pub group: Option, } @@ -479,8 +554,10 @@ pub struct User { ) )] pub struct Port { + /// The port number pub port: u16, + /// The protocol of the port #[serde(skip_serializing_if = "Option::is_none")] pub protocol: Option, } diff --git a/src/from_str.rs b/src/from_str.rs index cc23e0a..df792fb 100644 --- a/src/from_str.rs +++ b/src/from_str.rs @@ -209,7 +209,7 @@ impl_parsable_patch!(Bind, BindPatch, s, { }); impl_parsable_patch!(Cache, CachePatch, s, { - let regex = Regex::new(r"^(?:(?P[^:]+)(?::(?P\S+))? )?(?P\S+)$").unwrap(); + let regex = Regex::new(r"^(?:(?:(?Pimage|builder|context)\((?P[^:]+)\):)?(?P\S+) )?(?P\S+)$").unwrap(); let Some(captures) = regex.captures(s) else { return Err(Error::custom("Not matching bind pattern")); }; @@ -218,7 +218,18 @@ impl_parsable_patch!(Cache, CachePatch, s, { Ok(Self { source: Some(captures.name("source").map(|m| m.as_str().into())), target, - from: Some(captures.name("from").map(|m| m.as_str().into())), + from: captures.name("from").map(|m| { + let from_type = captures.name("fromType").map(|m| m.as_str()).unwrap(); + let from = m.as_str(); + match from_type { + "image" => { + FromContextPatch::FromImage(ImageNamePatch::from_str(from).unwrap().into()) + } + "builder" => FromContextPatch::FromBuilder(from.into()), + "context" => FromContextPatch::FromContext(Some(from.into())), + _ => unreachable!(), + } + }), chmod: Some(None), chown: Some(None), id: Some(None), diff --git a/src/generator.rs b/src/generator.rs index 0323a7e..1f5d127 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -565,8 +565,13 @@ impl DockerfileGenerator for Run { if let Some(id) = cache.id.as_ref() { cache_options.push(InstructionOptionOption::new("id", id.clone())); } - if let Some(from) = cache.from.as_ref() { - cache_options.push(InstructionOptionOption::new("from", from.clone())); + let from = match &cache.from { + FromContext::FromImage(image) => Some(image.to_string()), + FromContext::FromBuilder(builder) => Some(builder.clone()), + FromContext::FromContext(context) => context.clone(), + }; + if let Some(from) = from { + cache_options.push(InstructionOptionOption::new("from", from)); if let Some(source) = cache.source.as_ref() { cache_options.push(InstructionOptionOption::new("source", source.clone())); } @@ -634,6 +639,27 @@ impl Stage { for copy in self.copy.iter() { dependencies.append(&mut copy.get_dependencies()); } + dependencies.append(&mut self.run.get_dependencies()); + if let Some(root) = &self.root { + dependencies.append(&mut root.get_dependencies()); + } + dependencies + } +} + +impl Run { + pub(crate) fn get_dependencies(&self) -> Vec { + let mut dependencies = vec![]; + for cache in self.cache.iter() { + if let FromContext::FromBuilder(builder) = &cache.from { + dependencies.push(builder.clone()); + } + } + for bind in self.bind.iter() { + if let FromContext::FromBuilder(builder) = &bind.from { + dependencies.push(builder.clone()); + } + } dependencies } } diff --git a/src/lock.rs b/src/lock.rs index 2d00144..1304996 100644 --- a/src/lock.rs +++ b/src/lock.rs @@ -192,6 +192,13 @@ impl Lock for Stage { fn lock(&self, context: &mut DofigenContext) -> Result { Ok(Self { from: self.from.lock(context)?, + copy: self.copy.lock(context)?, + run: self.run.lock(context)?, + root: self + .root + .as_ref() + .map(|root| root.lock(context)) + .transpose()?, ..self.clone() }) } @@ -220,6 +227,52 @@ impl Lock for ImageName { } } +impl Lock for CopyResource { + fn lock(&self, context: &mut DofigenContext) -> Result { + match self { + Self::Copy(resource) => Ok(Self::Copy(resource.lock(context)?)), + other => Ok(other.clone()), + } + } +} + +impl Lock for Copy { + fn lock(&self, context: &mut DofigenContext) -> Result { + Ok(Self { + from: self.from.lock(context)?, + ..self.clone() + }) + } +} + +impl Lock for Run { + fn lock(&self, context: &mut DofigenContext) -> Result { + Ok(Self { + bind: self.bind.lock(context)?, + cache: self.cache.lock(context)?, + ..self.clone() + }) + } +} + +impl Lock for Bind { + fn lock(&self, context: &mut DofigenContext) -> Result { + Ok(Self { + from: self.from.lock(context)?, + ..self.clone() + }) + } +} + +impl Lock for Cache { + fn lock(&self, context: &mut DofigenContext) -> Result { + Ok(Self { + from: self.from.lock(context)?, + ..self.clone() + }) + } +} + impl Ord for DockerTag { fn cmp(&self, _other: &Self) -> std::cmp::Ordering { panic!("DockerTag cannot be ordered")