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/types",
|
||||||
"v1/v1util"
|
"v1/v1util"
|
||||||
]
|
]
|
||||||
revision = "71d387b23c8c5488e8a70a39b0b214af1ac01f30"
|
revision = "ee5a6c257df843b47a2666ff0fff3d31d484ebda"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/googleapis/gax-go"
|
name = "github.com/googleapis/gax-go"
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,8 @@ func GetCommand(cmd instructions.Command, buildcontext string) (DockerCommand, e
|
||||||
return &ArgCommand{cmd: c}, nil
|
return &ArgCommand{cmd: c}, nil
|
||||||
case *instructions.ShellCommand:
|
case *instructions.ShellCommand:
|
||||||
return &ShellCommand{cmd: c}, nil
|
return &ShellCommand{cmd: c}, nil
|
||||||
|
case *instructions.HealthCheckCommand:
|
||||||
|
return &HealthCheckCommand{cmd: c}, nil
|
||||||
case *instructions.MaintainerCommand:
|
case *instructions.MaintainerCommand:
|
||||||
logrus.Warnf("%s is deprecated, skipping", cmd.Name())
|
logrus.Warnf("%s is deprecated, skipping", cmd.Name())
|
||||||
return nil, nil
|
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 {
|
if d, err := NewDigest(s, strict); err == nil {
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
// TODO: Combine above errors into something more useful?
|
||||||
return nil, errors.New("could not parse reference")
|
return nil, errors.New("could not parse reference")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,27 @@ type RootFS struct {
|
||||||
DiffIDs []Hash `json:"diff_ids"`
|
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:
|
// Config is a submessage of the config file described as:
|
||||||
// The execution parameters which SHOULD be used as a base when running
|
// The execution parameters which SHOULD be used as a base when running
|
||||||
// a container using the image.
|
// a container using the image.
|
||||||
|
|
@ -77,6 +98,7 @@ type Config struct {
|
||||||
AttachStdin bool
|
AttachStdin bool
|
||||||
AttachStdout bool
|
AttachStdout bool
|
||||||
Cmd []string
|
Cmd []string
|
||||||
|
Healthcheck *HealthConfig
|
||||||
Domainname string
|
Domainname string
|
||||||
Entrypoint []string
|
Entrypoint []string
|
||||||
Env []string
|
Env []string
|
||||||
|
|
|
||||||
|
|
@ -300,7 +300,14 @@ func extract(img v1.Image, w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we have seen value before
|
// 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 {
|
if _, ok := fileMap[name]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
package random
|
package random
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -50,8 +51,15 @@ var _ partial.UncompressedLayer = (*uncompressedLayer)(nil)
|
||||||
func Image(byteSize, layers int64) (v1.Image, error) {
|
func Image(byteSize, layers int64) (v1.Image, error) {
|
||||||
layerz := make(map[v1.Hash]partial.UncompressedLayer)
|
layerz := make(map[v1.Hash]partial.UncompressedLayer)
|
||||||
for i := int64(0); i < layers; i++ {
|
for i := int64(0); i < layers; i++ {
|
||||||
b := bytes.NewBuffer(nil)
|
var b bytes.Buffer
|
||||||
if _, err := io.CopyN(b, rand.Reader, byteSize); err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
bts := b.Bytes()
|
bts := b.Bytes()
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"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)
|
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 {
|
if r.ref.Context().RegistryStr() == name.DefaultRegistry {
|
||||||
// TODO(docker/distribution#2395): Remove this check.
|
// TODO(docker/distribution#2395): Remove this check.
|
||||||
log.Println(err)
|
|
||||||
} else {
|
} else {
|
||||||
// When pulling by tag, we can only validate that the digest matches what the registry told us it should be.
|
// When pulling by tag, we can only validate that the digest matches what the registry told us it should be.
|
||||||
return nil, err
|
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?
|
// TODO(mattmoor): Whether to store things compressed?
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write saves the image as the given tag in a tarball at the given path.
|
// WriteToFile writes in the compressed format to a tarball, on disk.
|
||||||
func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
|
// This is just syntactic sugar wrapping tarball.Write with a new file.
|
||||||
// Write in the compressed format.
|
func WriteToFile(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
|
||||||
// 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.
|
|
||||||
|
|
||||||
w, err := os.Create(p)
|
w, err := os.Create(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer w.Close()
|
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)
|
tf := tar.NewWriter(w)
|
||||||
defer tf.Close()
|
defer tf.Close()
|
||||||
|
|
||||||
|
|
@ -58,7 +62,7 @@ func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,10 +98,9 @@ func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeFile(tf, layerFiles[i], r, blobSize); err != nil {
|
if err := writeTarEntry(tf, layerFiles[i], r, blobSize); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the tar descriptor and write it.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
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{
|
hdr := &tar.Header{
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
Typeflag: tar.TypeReg,
|
Typeflag: tar.TypeReg,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue