Skip to content

Commit

Permalink
add dockerfileContents to ImageBuild CRD (#140)
Browse files Browse the repository at this point in the history
* add dockerfileContents to CR

* use switch

* use existing temporary dir

* validations and trim

* add Digest to ImageBuild status

* CRD update

* add check for nil annotations map

* disable funlen check

* revert test changes

* better error message and change temp file perms to 0644
  • Loading branch information
dmcwhorter-ddl authored Apr 8, 2024
1 parent 73386c1 commit 79b8af9
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 15 deletions.
10 changes: 9 additions & 1 deletion deployments/crds/hephaestus.dominodatalab.com_imagebuilds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ spec:
type: string
type: array
context:
description: Context is a remote URL used to fetch the build context.
description: Context is a remote URL used to fetch the build context. Overrides
dockerfileContents if present.
type: string
disableBuildCache:
description: DisableLocalBuildCache will disable the use of the local
Expand All @@ -77,6 +78,10 @@ spec:
description: DisableCacheLayerExport will remove the "inline" cache
metadata from the image configuration.
type: boolean
dockerfileContents:
description: DockerfileContents specifies the contents of the Dockerfile
directly in the CR. Ignored if context is present.
type: string
images:
description: Images is a list of images to build and push.
items:
Expand Down Expand Up @@ -226,6 +231,9 @@ spec:
- type
type: object
type: array
digest:
description: Digest is the image digest
type: string
labels:
additionalProperties:
type: string
Expand Down
6 changes: 5 additions & 1 deletion pkg/api/hephaestus/v1/imagebuild_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ type ImageBuildAMQPOverrides struct {

// ImageBuildSpec specifies the desired state of an ImageBuild resource.
type ImageBuildSpec struct {
// Context is a remote URL used to fetch the build context.
// Context is a remote URL used to fetch the build context. Overrides dockerfileContents if present.
Context string `json:"context,omitempty"`
// DockerfileContents specifies the contents of the Dockerfile directly in the CR. Ignored if context is present.
DockerfileContents string `json:"dockerfileContents,omitempty"`
// Images is a list of images to build and push.
Images []string `json:"images,omitempty"`
// BuildArgs are applied to the build at runtime.
Expand Down Expand Up @@ -51,6 +53,8 @@ type ImageBuildStatus struct {
BuilderAddr string `json:"builderAddr,omitempty"`
// CompressedImageSizeBytes is the total size of all the compressed layers in the image.
CompressedImageSizeBytes string `json:"compressedImageSizeBytes,omitempty"`
// Digest is the image digest
Digest string `json:"digest,omitempty"`
// Map of string keys and values corresponding OCI image config labels.
// Labels contains arbitrary metadata for the container.
Labels map[string]string `json:"labels,omitempty"`
Expand Down
17 changes: 11 additions & 6 deletions pkg/api/hephaestus/v1/imagebuild_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,17 @@ func (in *ImageBuild) validateImageBuild(action string) (admission.Warnings, err
var errList field.ErrorList
fp := field.NewPath("spec")

if strings.TrimSpace(in.Spec.Context) == "" {
log.V(1).Info("Context is blank")
errList = append(errList, field.Required(fp.Child("context"), "must not be blank"))
} else if _, err := url.ParseRequestURI(in.Spec.Context); err != nil {
log.V(1).Info("Context is not a valid URL")
errList = append(errList, field.Invalid(fp.Child("context"), in.Spec.Context, err.Error()))
if strings.TrimSpace(in.Spec.Context) == "" && strings.TrimSpace(in.Spec.DockerfileContents) == "" {
log.V(1).Info("Context and DockerfileContents are both blank")
errList = append(errList, field.Required(fp.Child("context"), "must not be blank if "+
fp.Child("dockerfileContents").String()+" is blank"))
}

if strings.TrimSpace(in.Spec.Context) != "" {
if _, err := url.ParseRequestURI(in.Spec.Context); err != nil {
log.V(1).Info("Context is not a valid URL")
errList = append(errList, field.Invalid(fp.Child("context"), in.Spec.Context, err.Error()))
}
}

if errs := validateImages(log, fp.Child("images"), in.Spec.Images); errs != nil {
Expand Down
27 changes: 21 additions & 6 deletions pkg/buildkit/buildkit.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"io"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"time"

"github.com/containerd/console"
Expand Down Expand Up @@ -103,6 +105,7 @@ func (b *ClientBuilder) Build(ctx context.Context) (*Client, error) {
type BuildOptions struct {
Context string
ContextDir string
DockerfileContents string
Images []string
BuildArgs []string
NoCache bool
Expand Down Expand Up @@ -165,18 +168,30 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) (string, error) {

// process build context
var contentsDir string

if fi, err := os.Stat(opts.ContextDir); err == nil && fi.IsDir() {
fi, err := os.Stat(opts.ContextDir)
switch {
case err == nil && fi.IsDir():
c.log.Info("Using context dir", "dir", opts.ContextDir)
contentsDir = opts.ContextDir
} else {
case strings.TrimSpace(opts.Context) != "":
c.log.Info("Fetching remote context", "url", opts.Context)
extract, err := archive.FetchAndExtract(ctx, c.log, opts.Context, buildDir, opts.FetchAndExtractTimeout)
if err != nil {
extract, extractErr := archive.FetchAndExtract(ctx, c.log, opts.Context, buildDir, opts.FetchAndExtractTimeout)
if extractErr != nil {
return "", fmt.Errorf("cannot fetch remote context: %w", err)
}

contentsDir = extract.ContentsDir
case strings.TrimSpace(opts.DockerfileContents) != "":
c.log.Info("Creating context from DockerfileContents")
contentsDir, err = os.MkdirTemp(buildDir, "dockerfile-contents-")
if err != nil {
return "", fmt.Errorf("cannot create temp directory for dockerfileContents: %w", err)
}
err = os.WriteFile(path.Join(contentsDir, "Dockerfile"), []byte(opts.DockerfileContents), os.FileMode(0644))
if err != nil {
return "", fmt.Errorf("cannot write temporary file for dockerfileContents: %w", err)
}
default:
return "", errors.New("no valid docker context provided")
}
c.log.V(1).Info("Context extracted", "dir", contentsDir)

Expand Down
7 changes: 7 additions & 0 deletions pkg/controller/imagebuild/component/builddispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ func (c *BuildDispatcherComponent) Reconcile(coreCtx *core.Context) (ctrl.Result

buildOpts := buildkit.BuildOptions{
Context: obj.Spec.Context,
DockerfileContents: obj.Spec.DockerfileContents,
Images: obj.Spec.Images,
BuildArgs: obj.Spec.BuildArgs,
NoCache: obj.Spec.DisableLocalBuildCache,
Expand Down Expand Up @@ -278,9 +279,15 @@ func populateBuildStatus(obj *hephv1.ImageBuild, log logr.Logger, img v1.Image,
if err != nil {
log.Error(err, "Cannot calculate image labels", "imageName", imageName)
}
digest, err := img.Digest()
if err != nil {
log.Error(err, "Cannot retrieve image digest", "imageName", imageName)
}

log.Info(fmt.Sprintf("Final image size: %d", imageSize))

obj.Status.CompressedImageSizeBytes = strconv.FormatInt(imageSize, 10)
obj.Status.Digest = digest.String()
obj.Status.Labels = make(map[string]string)
for key, value := range labels {
if len(value) > 0 {
Expand Down
5 changes: 4 additions & 1 deletion pkg/controller/imagebuildmessage/component/amqpmessenger.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (c *AMQPMessengerComponent) Initialize(_ *core.Context, bldr *ctrl.Builder)
return nil
}

//nolint:maintidx
//nolint:maintidx,funlen
func (c *AMQPMessengerComponent) Reconcile(ctx *core.Context) (ctrl.Result, error) {
log := ctx.Log
obj := ctx.Object
Expand Down Expand Up @@ -205,6 +205,9 @@ func (c *AMQPMessengerComponent) Reconcile(ctx *core.Context) (ctrl.Result, erro
images = append(images, reference.TagNameOnly(named).String())
}
message.ImageURLs = images
if message.Annotations == nil {
message.Annotations = map[string]string{}
}
message.Annotations[compressedImageSizeBytesAnnotation] = ib.Status.CompressedImageSizeBytes
for key, value := range ib.Status.Labels {
message.Annotations[key] = value
Expand Down

0 comments on commit 79b8af9

Please sign in to comment.