WIP: Feature/healthcheck signal (#177)
* added basic healthcheck implementation * updated go-containerregistry version * added build args parameter to healthcheck execute * added go-containerregistry HealthCheck passing * dereferenced health for conversion
This commit is contained in:
parent
34e94608d4
commit
fbe3e05801
|
|
@ -393,7 +393,7 @@
|
|||
"v1/types",
|
||||
"v1/v1util"
|
||||
]
|
||||
revision = "71d387b23c8c5488e8a70a39b0b214af1ac01f30"
|
||||
revision = "ee5a6c257df843b47a2666ff0fff3d31d484ebda"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/googleapis/gax-go"
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ func GetCommand(cmd instructions.Command, buildcontext string) (DockerCommand, e
|
|||
return &ArgCommand{cmd: c}, nil
|
||||
case *instructions.ShellCommand:
|
||||
return &ShellCommand{cmd: c}, nil
|
||||
case *instructions.HealthCheckCommand:
|
||||
return &HealthCheckCommand{cmd: c}, nil
|
||||
case *instructions.MaintainerCommand:
|
||||
logrus.Warnf("%s is deprecated, skipping", cmd.Name())
|
||||
return nil, nil
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
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
|
||||
|
||||
http://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.
|
||||
*/
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type HealthCheckCommand struct {
|
||||
cmd *instructions.HealthCheckCommand
|
||||
}
|
||||
|
||||
// ExecuteCommand handles command processing similar to CMD and RUN,
|
||||
func (h *HealthCheckCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
logrus.Info("cmd: HEALTHCHECK")
|
||||
|
||||
check := v1.HealthConfig(*h.cmd.Health)
|
||||
config.Healthcheck = &check
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilesToSnapshot returns an empty array since this is a metadata command
|
||||
func (h *HealthCheckCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (h *HealthCheckCommand) CreatedBy() string {
|
||||
entrypoint := []string{"HEALTHCHECK"}
|
||||
|
||||
return strings.Join(append(entrypoint, strings.Join(h.cmd.Health.Test, " ")), " ")
|
||||
}
|
||||
|
|
@ -45,5 +45,6 @@ func ParseReference(s string, strict Strictness) (Reference, error) {
|
|||
if d, err := NewDigest(s, strict); err == nil {
|
||||
return d, nil
|
||||
}
|
||||
// TODO: Combine above errors into something more useful?
|
||||
return nil, errors.New("could not parse reference")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,27 @@ type RootFS struct {
|
|||
DiffIDs []Hash `json:"diff_ids"`
|
||||
}
|
||||
|
||||
// HealthConfig holds configuration settings for the HEALTHCHECK feature.
|
||||
type HealthConfig struct {
|
||||
// Test is the test to perform to check that the container is healthy.
|
||||
// An empty slice means to inherit the default.
|
||||
// The options are:
|
||||
// {} : inherit healthcheck
|
||||
// {"NONE"} : disable healthcheck
|
||||
// {"CMD", args...} : exec arguments directly
|
||||
// {"CMD-SHELL", command} : run command with system's default shell
|
||||
Test []string `json:",omitempty"`
|
||||
|
||||
// Zero means to inherit. Durations are expressed as integer nanoseconds.
|
||||
Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
|
||||
Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
|
||||
StartPeriod time.Duration `json:",omitempty"` // The start period for the container to initialize before the retries starts to count down.
|
||||
|
||||
// Retries is the number of consecutive failures needed to consider a container as unhealthy.
|
||||
// Zero means inherit.
|
||||
Retries int `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Config is a submessage of the config file described as:
|
||||
// The execution parameters which SHOULD be used as a base when running
|
||||
// a container using the image.
|
||||
|
|
@ -77,6 +98,7 @@ type Config struct {
|
|||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
Cmd []string
|
||||
Healthcheck *HealthConfig
|
||||
Domainname string
|
||||
Entrypoint []string
|
||||
Env []string
|
||||
|
|
|
|||
|
|
@ -300,7 +300,14 @@ func extract(img v1.Image, w io.Writer) error {
|
|||
}
|
||||
|
||||
// check if we have seen value before
|
||||
name := filepath.Join(dirname, basename)
|
||||
// if we're checking a directory, don't filepath.Join names
|
||||
var name string
|
||||
if header.Typeflag == tar.TypeDir {
|
||||
name = header.Name
|
||||
} else {
|
||||
name = filepath.Join(dirname, basename)
|
||||
}
|
||||
|
||||
if _, ok := fileMap[name]; ok {
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
|
@ -50,8 +51,15 @@ var _ partial.UncompressedLayer = (*uncompressedLayer)(nil)
|
|||
func Image(byteSize, layers int64) (v1.Image, error) {
|
||||
layerz := make(map[v1.Hash]partial.UncompressedLayer)
|
||||
for i := int64(0); i < layers; i++ {
|
||||
b := bytes.NewBuffer(nil)
|
||||
if _, err := io.CopyN(b, rand.Reader, byteSize); err != nil {
|
||||
var b bytes.Buffer
|
||||
tw := tar.NewWriter(&b)
|
||||
if err := tw.WriteHeader(&tar.Header{
|
||||
Name: fmt.Sprintf("random_file_%d.txt", i),
|
||||
Size: byteSize,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.CopyN(tw, rand.Reader, byteSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bts := b.Bytes()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
|
@ -115,7 +114,6 @@ func (r *remoteImage) RawManifest() ([]byte, error) {
|
|||
err := fmt.Errorf("manifest digest: %q does not match Docker-Content-Digest: %q for %q", digest, checksum, r.ref)
|
||||
if r.ref.Context().RegistryStr() == name.DefaultRegistry {
|
||||
// TODO(docker/distribution#2395): Remove this check.
|
||||
log.Println(err)
|
||||
} else {
|
||||
// When pulling by tag, we can only validate that the digest matches what the registry told us it should be.
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2018 Google LLC 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
|
||||
//
|
||||
// http://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.
|
||||
|
||||
package remote
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/google/go-containerregistry/authn"
|
||||
"github.com/google/go-containerregistry/name"
|
||||
"github.com/google/go-containerregistry/v1/remote/transport"
|
||||
)
|
||||
|
||||
type Tags struct {
|
||||
Name string `json:"name"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
// TODO(jonjohnsonjr): return []name.Tag?
|
||||
func List(repo name.Repository, auth authn.Authenticator, t http.RoundTripper) ([]string, error) {
|
||||
scopes := []string{repo.Scope(transport.PullScope)}
|
||||
tr, err := transport.New(repo.Registry, auth, t, scopes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uri := url.URL{
|
||||
Scheme: transport.Scheme(repo.Registry),
|
||||
Host: repo.Registry.RegistryStr(),
|
||||
Path: fmt.Sprintf("/v2/%s/tags/list", repo.RepositoryStr()),
|
||||
}
|
||||
|
||||
client := http.Client{Transport: tr}
|
||||
resp, err := client.Get(uri.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := checkError(resp, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags := Tags{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&tags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tags.Tags, nil
|
||||
}
|
||||
|
|
@ -32,20 +32,24 @@ type WriteOptions struct {
|
|||
// TODO(mattmoor): Whether to store things compressed?
|
||||
}
|
||||
|
||||
// Write saves the image as the given tag in a tarball at the given path.
|
||||
func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
|
||||
// Write in the compressed format.
|
||||
// This is a tarball, on-disk, with:
|
||||
// One manifest.json file at the top level containing information about several images.
|
||||
// One file for each layer, named after the layer's SHA.
|
||||
// One file for the config blob, named after its SHA.
|
||||
|
||||
// WriteToFile writes in the compressed format to a tarball, on disk.
|
||||
// This is just syntactic sugar wrapping tarball.Write with a new file.
|
||||
func WriteToFile(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
|
||||
w, err := os.Create(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
return Write(tag, img, wo, w)
|
||||
}
|
||||
|
||||
// Write the contents of the image to the provided reader, in the compressed format.
|
||||
// The contents are written in the following format:
|
||||
// One manifest.json file at the top level containing information about several images.
|
||||
// One file for each layer, named after the layer's SHA.
|
||||
// One file for the config blob, named after its SHA.
|
||||
func Write(tag name.Tag, img v1.Image, wo *WriteOptions, w io.Writer) error {
|
||||
tf := tar.NewWriter(w)
|
||||
defer tf.Close()
|
||||
|
||||
|
|
@ -58,7 +62,7 @@ func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile(tf, cfgName.String(), bytes.NewReader(cfgBlob), int64(len(cfgBlob))); err != nil {
|
||||
if err := writeTarEntry(tf, cfgName.String(), bytes.NewReader(cfgBlob), int64(len(cfgBlob))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -94,10 +98,9 @@ func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := writeFile(tf, layerFiles[i], r, blobSize); err != nil {
|
||||
if err := writeTarEntry(tf, layerFiles[i], r, blobSize); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Generate the tar descriptor and write it.
|
||||
|
|
@ -112,10 +115,11 @@ func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeFile(tf, "manifest.json", bytes.NewReader(tdBytes), int64(len(tdBytes)))
|
||||
return writeTarEntry(tf, "manifest.json", bytes.NewReader(tdBytes), int64(len(tdBytes)))
|
||||
}
|
||||
|
||||
func writeFile(tf *tar.Writer, path string, r io.Reader, size int64) error {
|
||||
// write a file to the provided writer with a corresponding tar header
|
||||
func writeTarEntry(tf *tar.Writer, path string, r io.Reader, size int64) error {
|
||||
hdr := &tar.Header{
|
||||
Mode: 0644,
|
||||
Typeflag: tar.TypeReg,
|
||||
|
|
|
|||
Loading…
Reference in New Issue