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:
Christian Jantz
2018-05-14 14:28:24 -07:00
committed by dlorenc
parent 34e94608d4
commit fbe3e05801
10 changed files with 177 additions and 19 deletions
+1
View File
@@ -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")
}
+22
View File
@@ -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
+8 -1
View File
@@ -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
}
+10 -2
View File
@@ -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()
-2
View File
@@ -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
+64
View File
@@ -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
}
+17 -13
View File
@@ -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,