From d9acb01c15904c4c7ac34fc9754242e1f9816186 Mon Sep 17 00:00:00 2001 From: Thomas Winant Date: Mon, 13 May 2024 12:01:37 +0200 Subject: [PATCH] Allow setting the Created timestamp Implements #89. Like `nixpkgs.dockerTools.buildImage`, let `nix2container.buildImage` take an optional `created` argument that will be set on the resulting image. For backwards compatibility, it defaults to `"0001-01-01T00:00:00Z"`. I did not implement support for passing `"now"`, as I don't need it. (cherry picked from commit ffafa5df41829395609c51b25853fd7bdf154e5e) --- cmd/image.go | 27 +++++++++++++++++++++++++-- default.nix | 4 ++++ examples/created.nix | 8 ++++++++ examples/default.nix | 1 + nix/image.go | 1 + tests/default.nix | 18 ++++++++++++++++++ types/types.go | 2 ++ 7 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 examples/created.nix diff --git a/cmd/image.go b/cmd/image.go index b0ae74d..3dffbdd 100644 --- a/cmd/image.go +++ b/cmd/image.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "runtime" + "time" "github.com/nlewo/nix2container/nix" "github.com/nlewo/nix2container/types" @@ -15,12 +16,30 @@ import ( var fromImageFilename string +var created timeValue + +type timeValue time.Time + +func (tv *timeValue) String() string { + return (*time.Time)(tv).Format(time.RFC3339) +} + +func (tv *timeValue) Set(value string) error { + t, err := time.Parse(time.RFC3339, value) + *tv = timeValue(t) + return err +} + +func (tv *timeValue) Type() string { + return "time" +} + var imageCmd = &cobra.Command{ Use: "image OUTPUT-FILENAME CONFIG.JSON LAYERS-1.JSON LAYERS-2.JSON ...", Short: "Generate an image.json file from a image configuration and layers", Args: cobra.MinimumNArgs(3), Run: func(cmd *cobra.Command, args []string) { - err := image(args[0], args[1], fromImageFilename, args[2:]) + err := image(args[0], args[1], fromImageFilename, args[2:], (time.Time)(created)) if err != nil { fmt.Fprintf(os.Stderr, "%s", err) os.Exit(1) @@ -88,7 +107,7 @@ func imageFromManifest(outputFilename, manifestFilename string, blobsFilename st return nil } -func image(outputFilename, imageConfigPath string, fromImageFilename string, layerPaths []string) error { +func image(outputFilename, imageConfigPath string, fromImageFilename string, layerPaths []string, created time.Time) error { var imageConfig v1.ImageConfig var image types.Image @@ -117,6 +136,9 @@ func image(outputFilename, imageConfigPath string, fromImageFilename string, lay image.Arch = runtime.GOARCH image.ImageConfig = imageConfig + + image.Created = &created + for _, path := range layerPaths { var layers []types.Layer layerJson, err := os.ReadFile(path) @@ -145,6 +167,7 @@ func image(outputFilename, imageConfigPath string, fromImageFilename string, lay func init() { rootCmd.AddCommand(imageCmd) imageCmd.Flags().StringVarP(&fromImageFilename, "from-image", "", "", "A JSON file describing the base image") + imageCmd.Flags().Var(&created, "created", "Timestamp at which the image was created") rootCmd.AddCommand(imageFromDirCmd) rootCmd.AddCommand(imageFromManifestCmd) } diff --git a/default.nix b/default.nix index 99b02a7..a29ff92 100644 --- a/default.nix +++ b/default.nix @@ -391,6 +391,8 @@ let # controlled using nixUid/nixGid. nixUid ? 0, nixGid ? 0, + # Time of creation of the image. + created ? "0001-01-01T00:00:00Z", # Deprecated: will be removed contents ? null, meta ? {}, @@ -440,6 +442,7 @@ let layers = layers; }; fromImageFlag = l.optionalString (fromImage != "") "--from-image ${fromImage}"; + createdFlag = "--created ${created}"; layerPaths = l.concatMapStringsSep " " (l: l + "/layers.json") (layers ++ [customizationLayer]); image = let imageName = l.toLower name; @@ -467,6 +470,7 @@ let ${nix2container-bin}/bin/nix2container image \ $out \ ${fromImageFlag} \ + ${createdFlag} \ ${configFile} \ ${layerPaths} ''; diff --git a/examples/created.nix b/examples/created.nix new file mode 100644 index 0000000..f2be680 --- /dev/null +++ b/examples/created.nix @@ -0,0 +1,8 @@ +{ pkgs, nix2container }: +nix2container.buildImage { + name = "created"; + config = { + entrypoint = ["${pkgs.hello}/bin/hello"]; + }; + created = "2024-05-13T09:31:10Z"; +} diff --git a/examples/default.nix b/examples/default.nix index 79f8336..104da7b 100644 --- a/examples/default.nix +++ b/examples/default.nix @@ -14,4 +14,5 @@ nix = pkgs.callPackage ./nix.nix { inherit nix2container; }; nix-user = pkgs.callPackage ./nix-user.nix { inherit nix2container; }; ownership = pkgs.callPackage ./ownership.nix { inherit nix2container; }; + created = pkgs.callPackage ./created.nix { inherit nix2container; }; } diff --git a/nix/image.go b/nix/image.go index 07dbd52..b9c7027 100644 --- a/nix/image.go +++ b/nix/image.go @@ -73,6 +73,7 @@ func getV1Image(image types.Image) (imageV1 v1.Image, err error) { imageV1.OS = "linux" imageV1.Architecture = image.Arch imageV1.Config = image.ImageConfig + imageV1.Created = image.Created for _, layer := range image.Layers { digest, err := godigest.Parse(layer.DiffIDs) diff --git a/tests/default.nix b/tests/default.nix index 2897dff..a0b0b7f 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -137,6 +137,24 @@ let }; pattern = "Hello, world!"; }; + created = let + image = examples.created; + timestamp = "2024-05-13 09:31:10"; + in pkgs.writeScriptBin "test-script" '' + ${image.copyToPodman}/bin/copy-to-podman + created=$(${pkgs.podman}/bin/podman image inspect ${image.imageName}:${image.imageTag} -f '{{ .Created }}') + if echo $created | ${pkgs.gnugrep}/bin/grep '${timestamp}' > /dev/null; + then + echo "Test passed" + else + echo "Expected Created attribute to contain: ${timestamp}" + echo "" + echo "Actual Created attribute: $created" + echo "" + echo "Error: test failed" + exit $ret + fi + ''; } // (pkgs.lib.mapAttrs' (name: drv: { name = "${name}GetManifest"; diff --git a/types/types.go b/types/types.go index 41ccc73..66f3bed 100644 --- a/types/types.go +++ b/types/types.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io" "os" + "time" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -18,6 +19,7 @@ type Image struct { ImageConfig v1.ImageConfig `json:"image-config"` Layers []Layer `json:"layers"` Arch string `json:"arch"` + Created *time.Time `json:"created"` } type Rewrite struct {