Add image load.

Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
Lantao Liu 2017-10-26 05:59:15 +00:00
parent c6fd18ddc3
commit 25fdf72692
20 changed files with 1316 additions and 104 deletions

View File

@ -48,6 +48,7 @@ help:
@echo " * 'test-e2e-node' - Test cri-containerd with Kubernetes node e2e test" @echo " * 'test-e2e-node' - Test cri-containerd with Kubernetes node e2e test"
@echo " * 'clean' - Clean artifacts" @echo " * 'clean' - Clean artifacts"
@echo " * 'verify' - Execute the source code verification tools" @echo " * 'verify' - Execute the source code verification tools"
@echo " * 'proto' - Update protobuf of cri-containerd api"
@echo " * 'install.tools' - Install tools used by verify" @echo " * 'install.tools' - Install tools used by verify"
@echo " * 'install.deps' - Install dependencies of cri-containerd (containerd, runc, cni) Note: BUILDTAGS defaults to 'seccomp apparmor' for runc build" @echo " * 'install.deps' - Install dependencies of cri-containerd (containerd, runc, cni) Note: BUILDTAGS defaults to 'seccomp apparmor' for runc build"
@echo " * 'uninstall' - Remove installed binaries from system locations" @echo " * 'uninstall' - Remove installed binaries from system locations"
@ -117,6 +118,9 @@ release: $(BUILD_DIR)/$(TARBALL)
push: $(BUILD_DIR)/$(TARBALL) push: $(BUILD_DIR)/$(TARBALL)
@BUILD_DIR=$(BUILD_DIR) TARBALL=$(TARBALL) VERSION=$(VERSION) ./hack/push.sh @BUILD_DIR=$(BUILD_DIR) TARBALL=$(TARBALL) VERSION=$(VERSION) ./hack/push.sh
proto:
@hack/update-proto.sh
.PHONY: install.deps .PHONY: install.deps
install.deps: install.deps:
@ -160,4 +164,5 @@ install.tools: .install.gitvalidation .install.gometalinter
test-cri \ test-cri \
test-e2e-node \ test-e2e-node \
uninstall \ uninstall \
version version \
proto

View File

@ -18,15 +18,20 @@ package main
import ( import (
"flag" "flag"
"fmt"
"os" "os"
"path/filepath"
"github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/reexec"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/opencontainers/selinux/go-selinux" "github.com/opencontainers/selinux/go-selinux"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/util/interrupt" "k8s.io/kubernetes/pkg/util/interrupt"
"github.com/kubernetes-incubator/cri-containerd/cmd/cri-containerd/options" "github.com/kubernetes-incubator/cri-containerd/cmd/cri-containerd/options"
api "github.com/kubernetes-incubator/cri-containerd/pkg/api/v1"
"github.com/kubernetes-incubator/cri-containerd/pkg/client"
"github.com/kubernetes-incubator/cri-containerd/pkg/server" "github.com/kubernetes-incubator/cri-containerd/pkg/server"
"github.com/kubernetes-incubator/cri-containerd/pkg/version" "github.com/kubernetes-incubator/cri-containerd/pkg/version"
) )
@ -72,6 +77,35 @@ func versionCommand() *cobra.Command {
} }
} }
func loadImageCommand() *cobra.Command {
c := &cobra.Command{
Use: "load TAR",
Short: "Load an image from a tar archive.",
Args: cobra.ExactArgs(1),
}
endpoint, timeout := options.AddGRPCFlags(c.Flags())
c.RunE = func(cmd *cobra.Command, args []string) error {
cl, err := client.NewCRIContainerdClient(*endpoint, *timeout)
if err != nil {
return fmt.Errorf("failed to create grpc client: %v", err)
}
path, err := filepath.Abs(args[0])
if err != nil {
return fmt.Errorf("failed to get absolute path: %v", err)
}
res, err := cl.LoadImage(context.Background(), &api.LoadImageRequest{FilePath: path})
if err != nil {
return fmt.Errorf("failed to load image: %v", err)
}
images := res.GetImages()
for _, image := range images {
fmt.Println("Loaded image:", image)
}
return nil
}
return c
}
func main() { func main() {
if reexec.Init() { if reexec.Init() {
return return
@ -81,10 +115,11 @@ func main() {
o.AddFlags(cmd.Flags()) o.AddFlags(cmd.Flags())
cmd.AddCommand(defaultConfigCommand()) cmd.AddCommand(defaultConfigCommand())
cmd.AddCommand(versionCommand()) cmd.AddCommand(versionCommand())
cmd.AddCommand(loadImageCommand())
cmd.Run = func(cmd *cobra.Command, args []string) { cmd.RunE = func(cmd *cobra.Command, args []string) error {
if err := o.InitFlags(cmd.Flags()); err != nil { if err := o.InitFlags(cmd.Flags()); err != nil {
glog.Exitf("Failed to init CRI containerd flags: %v", err) return fmt.Errorf("failed to init CRI containerd flags: %v", err)
} }
validateConfig(o) validateConfig(o)
@ -93,19 +128,21 @@ func main() {
glog.V(2).Infof("Run cri-containerd grpc server on socket %q", o.SocketPath) glog.V(2).Infof("Run cri-containerd grpc server on socket %q", o.SocketPath)
s, err := server.NewCRIContainerdService(o.Config) s, err := server.NewCRIContainerdService(o.Config)
if err != nil { if err != nil {
glog.Exitf("Failed to create CRI containerd service: %v", err) return fmt.Errorf("failed to create CRI containerd service: %v", err)
} }
// Use interrupt handler to make sure the server is stopped properly. // Use interrupt handler to make sure the server is stopped properly.
// Pass in non-empty final function to avoid os.Exit(1). We expect `Run` // Pass in non-empty final function to avoid os.Exit(1). We expect `Run`
// to return itself. // to return itself.
h := interrupt.New(func(os.Signal) {}, s.Stop) h := interrupt.New(func(os.Signal) {}, s.Stop)
if err := h.Run(func() error { return s.Run() }); err != nil { if err := h.Run(func() error { return s.Run() }); err != nil {
glog.Exitf("Failed to run cri-containerd grpc server: %v", err) return fmt.Errorf("failed to run cri-containerd grpc server: %v", err)
} }
return nil
} }
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
glog.Exitf("Failed to execute cri-containerd: %v", err) // Error should have been reported.
os.Exit(1)
} }
} }

View File

@ -19,6 +19,7 @@ package options
import ( import (
"fmt" "fmt"
"os" "os"
"time"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/containerd/containerd" "github.com/containerd/containerd"
@ -30,6 +31,8 @@ const (
configFilePathArgName = "config" configFilePathArgName = "config"
// defaultConfigFilePath is the default config file path. // defaultConfigFilePath is the default config file path.
defaultConfigFilePath = "/etc/cri-containerd/config.toml" defaultConfigFilePath = "/etc/cri-containerd/config.toml"
// connectionTimeout is the grpc connection timeout.
connectionTimeout = 10 * time.Second
) )
// ContainerdConfig contains config related to containerd // ContainerdConfig contains config related to containerd
@ -178,6 +181,13 @@ func PrintDefaultTomlConfig() {
} }
} }
// AddGRPCFlags add flags for grpc connection.
func AddGRPCFlags(fs *pflag.FlagSet) (*string, *time.Duration) {
endpoint := fs.String("endpoint", defaultConfig().SocketPath, "cri-containerd endpoint.")
timeout := fs.Duration("timeout", connectionTimeout, "cri-containerd connection timeout.")
return endpoint, timeout
}
// defaultConfig returns default configurations of cri-containerd. // defaultConfig returns default configurations of cri-containerd.
func defaultConfig() Config { func defaultConfig() Config {
return Config{ return Config{

45
hack/update-proto.sh Executable file
View File

@ -0,0 +1,45 @@
#!/bin/bash
# Copyright 2017 The Kubernetes Authors.
#
# 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.
set -o errexit
set -o nounset
set -o pipefail
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
API_ROOT="${ROOT}/pkg/api/v1"
go get k8s.io/code-generator/cmd/go-to-protobuf/protoc-gen-gogo
if ! which protoc-gen-gogo >/dev/null; then
echo "GOPATH is not in PATH"
exit 1
fi
function cleanup {
rm -f ${API_ROOT}/api.pb.go.bak
}
trap cleanup EXIT
protoc \
--proto_path="${API_ROOT}" \
--proto_path="${ROOT}/vendor" \
--gogo_out=plugins=grpc:${API_ROOT} ${API_ROOT}/api.proto
# Update boilerplate for the generated file.
echo "$(cat hack/boilerplate/boilerplate.go.txt ${API_ROOT}/api.pb.go)" > ${API_ROOT}/api.pb.go
sed -i".bak" "s/Copyright YEAR/Copyright $(date '+%Y')/g" ${API_ROOT}/api.pb.go
gofmt -l -s -w ${API_ROOT}/api.pb.go

View File

@ -17,7 +17,7 @@ set -o errexit
set -o nounset set -o nounset
set -o pipefail set -o pipefail
for d in $(find . -type d -a \( -iwholename './pkg*' -o -iwholename './cmd*' \)); do for d in $(find . -type d -a \( -iwholename './pkg*' -o -iwholename './cmd*' \) -not -iwholename './pkg/api*'); do
echo for directory ${d} ... echo for directory ${d} ...
gometalinter \ gometalinter \
--exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \ --exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \

598
pkg/api/v1/api.pb.go Normal file
View File

@ -0,0 +1,598 @@
/*
Copyright 2017 The Kubernetes Authors.
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.
*/
// Code generated by protoc-gen-gogo.
// source: api.proto
// DO NOT EDIT!
/*
Package api_v1 is a generated protocol buffer package.
It is generated from these files:
api.proto
It has these top-level messages:
LoadImageRequest
LoadImageResponse
*/
package api_v1
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import _ "github.com/gogo/protobuf/gogoproto"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
import strings "strings"
import reflect "reflect"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type LoadImageRequest struct {
// FilePath is the absolute path of docker image tarball.
FilePath string `protobuf:"bytes,1,opt,name=FilePath,proto3" json:"FilePath,omitempty"`
}
func (m *LoadImageRequest) Reset() { *m = LoadImageRequest{} }
func (*LoadImageRequest) ProtoMessage() {}
func (*LoadImageRequest) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{0} }
func (m *LoadImageRequest) GetFilePath() string {
if m != nil {
return m.FilePath
}
return ""
}
type LoadImageResponse struct {
// Images have been loaded.
Images []string `protobuf:"bytes,1,rep,name=Images" json:"Images,omitempty"`
}
func (m *LoadImageResponse) Reset() { *m = LoadImageResponse{} }
func (*LoadImageResponse) ProtoMessage() {}
func (*LoadImageResponse) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{1} }
func (m *LoadImageResponse) GetImages() []string {
if m != nil {
return m.Images
}
return nil
}
func init() {
proto.RegisterType((*LoadImageRequest)(nil), "api.v1.LoadImageRequest")
proto.RegisterType((*LoadImageResponse)(nil), "api.v1.LoadImageResponse")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for CRIContainerdService service
type CRIContainerdServiceClient interface {
// LoadImage loads a image into containerd.
LoadImage(ctx context.Context, in *LoadImageRequest, opts ...grpc.CallOption) (*LoadImageResponse, error)
}
type cRIContainerdServiceClient struct {
cc *grpc.ClientConn
}
func NewCRIContainerdServiceClient(cc *grpc.ClientConn) CRIContainerdServiceClient {
return &cRIContainerdServiceClient{cc}
}
func (c *cRIContainerdServiceClient) LoadImage(ctx context.Context, in *LoadImageRequest, opts ...grpc.CallOption) (*LoadImageResponse, error) {
out := new(LoadImageResponse)
err := grpc.Invoke(ctx, "/api.v1.CRIContainerdService/LoadImage", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for CRIContainerdService service
type CRIContainerdServiceServer interface {
// LoadImage loads a image into containerd.
LoadImage(context.Context, *LoadImageRequest) (*LoadImageResponse, error)
}
func RegisterCRIContainerdServiceServer(s *grpc.Server, srv CRIContainerdServiceServer) {
s.RegisterService(&_CRIContainerdService_serviceDesc, srv)
}
func _CRIContainerdService_LoadImage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LoadImageRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CRIContainerdServiceServer).LoadImage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/api.v1.CRIContainerdService/LoadImage",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CRIContainerdServiceServer).LoadImage(ctx, req.(*LoadImageRequest))
}
return interceptor(ctx, in, info, handler)
}
var _CRIContainerdService_serviceDesc = grpc.ServiceDesc{
ServiceName: "api.v1.CRIContainerdService",
HandlerType: (*CRIContainerdServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "LoadImage",
Handler: _CRIContainerdService_LoadImage_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api.proto",
}
func (m *LoadImageRequest) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *LoadImageRequest) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.FilePath) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintApi(dAtA, i, uint64(len(m.FilePath)))
i += copy(dAtA[i:], m.FilePath)
}
return i, nil
}
func (m *LoadImageResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *LoadImageResponse) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Images) > 0 {
for _, s := range m.Images {
dAtA[i] = 0xa
i++
l = len(s)
for l >= 1<<7 {
dAtA[i] = uint8(uint64(l)&0x7f | 0x80)
l >>= 7
i++
}
dAtA[i] = uint8(l)
i++
i += copy(dAtA[i:], s)
}
}
return i, nil
}
func encodeFixed64Api(dAtA []byte, offset int, v uint64) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
dAtA[offset+4] = uint8(v >> 32)
dAtA[offset+5] = uint8(v >> 40)
dAtA[offset+6] = uint8(v >> 48)
dAtA[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32Api(dAtA []byte, offset int, v uint32) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintApi(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *LoadImageRequest) Size() (n int) {
var l int
_ = l
l = len(m.FilePath)
if l > 0 {
n += 1 + l + sovApi(uint64(l))
}
return n
}
func (m *LoadImageResponse) Size() (n int) {
var l int
_ = l
if len(m.Images) > 0 {
for _, s := range m.Images {
l = len(s)
n += 1 + l + sovApi(uint64(l))
}
}
return n
}
func sovApi(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozApi(x uint64) (n int) {
return sovApi(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (this *LoadImageRequest) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&LoadImageRequest{`,
`FilePath:` + fmt.Sprintf("%v", this.FilePath) + `,`,
`}`,
}, "")
return s
}
func (this *LoadImageResponse) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&LoadImageResponse{`,
`Images:` + fmt.Sprintf("%v", this.Images) + `,`,
`}`,
}, "")
return s
}
func valueToStringApi(v interface{}) string {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return "nil"
}
pv := reflect.Indirect(rv).Interface()
return fmt.Sprintf("*%v", pv)
}
func (m *LoadImageRequest) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: LoadImageRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: LoadImageRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field FilePath", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthApi
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.FilePath = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipApi(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthApi
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *LoadImageResponse) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: LoadImageResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: LoadImageResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Images", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthApi
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Images = append(m.Images, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipApi(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthApi
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipApi(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthApi
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipApi(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthApi = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowApi = fmt.Errorf("proto: integer overflow")
)
func init() { proto.RegisterFile("api.proto", fileDescriptorApi) }
var fileDescriptorApi = []byte{
// 223 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0x2c, 0xc8, 0xd4,
0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x03, 0x31, 0xcb, 0x0c, 0xa5, 0x74, 0xd3, 0x33, 0x4b,
0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0xd3, 0xf3, 0xd3, 0xf3, 0xf5, 0xc1, 0xd2, 0x49,
0xa5, 0x69, 0x60, 0x1e, 0x98, 0x03, 0x66, 0x41, 0xb4, 0x29, 0xe9, 0x71, 0x09, 0xf8, 0xe4, 0x27,
0xa6, 0x78, 0xe6, 0x26, 0xa6, 0xa7, 0x06, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x49, 0x71,
0x71, 0xb8, 0x65, 0xe6, 0xa4, 0x06, 0x24, 0x96, 0x64, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06,
0xc1, 0xf9, 0x4a, 0xda, 0x5c, 0x82, 0x48, 0xea, 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xc4,
0xb8, 0xd8, 0xc0, 0x02, 0xc5, 0x12, 0x8c, 0x0a, 0xcc, 0x1a, 0x9c, 0x41, 0x50, 0x9e, 0x51, 0x14,
0x97, 0x88, 0x73, 0x90, 0xa7, 0x73, 0x7e, 0x5e, 0x49, 0x62, 0x66, 0x5e, 0x6a, 0x51, 0x4a, 0x70,
0x6a, 0x51, 0x59, 0x66, 0x72, 0xaa, 0x90, 0x13, 0x17, 0x27, 0xdc, 0x10, 0x21, 0x09, 0x3d, 0x88,
0xcb, 0xf5, 0xd0, 0xdd, 0x21, 0x25, 0x89, 0x45, 0x06, 0x62, 0xa3, 0x12, 0x83, 0x93, 0xcc, 0x89,
0x87, 0x72, 0x8c, 0x37, 0x1e, 0xca, 0x31, 0x34, 0x3c, 0x92, 0x63, 0x3c, 0xf1, 0x48, 0x8e, 0xf1,
0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0xfb,
0xce, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x6a, 0xfe, 0x35, 0x81, 0x21, 0x01, 0x00, 0x00,
}

30
pkg/api/v1/api.proto Normal file
View File

@ -0,0 +1,30 @@
// To regenerate api.pb.go run `make proto`hack/update-generated-runtime.sh
syntax = 'proto3';
package api.v1;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option (gogoproto.goproto_stringer_all) = false;
option (gogoproto.stringer_all) = true;
option (gogoproto.goproto_getters_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.goproto_unrecognized_all) = false;
// CRIContainerdService defines non-CRI APIs for cri-containerd.
service CRIContainerdService{
// LoadImage loads a image into containerd.
rpc LoadImage(LoadImageRequest) returns (LoadImageResponse) {}
}
message LoadImageRequest {
// FilePath is the absolute path of docker image tarball.
string FilePath = 1;
}
message LoadImageResponse {
// Images have been loaded.
repeated string Images = 1;
}

45
pkg/client/client.go Normal file
View File

@ -0,0 +1,45 @@
/*
Copyright 2017 The Kubernetes Authors.
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 client
import (
"fmt"
"time"
"google.golang.org/grpc"
"k8s.io/kubernetes/pkg/kubelet/util"
api "github.com/kubernetes-incubator/cri-containerd/pkg/api/v1"
)
// NewCRIContainerdClient creates grpc client of cri-containerd
// TODO(random-liu): Wrap grpc functions.
func NewCRIContainerdClient(endpoint string, timeout time.Duration) (api.CRIContainerdServiceClient, error) {
addr, dailer, err := util.GetAddressAndDialer(endpoint)
if err != nil {
return nil, fmt.Errorf("failed to get dialer: %v", err)
}
conn, err := grpc.Dial(addr,
grpc.WithInsecure(),
grpc.WithTimeout(timeout),
grpc.WithDialer(dailer),
)
if err != nil {
return nil, fmt.Errorf("failed to dial: %v", err)
}
return api.NewCRIContainerdServiceClient(conn), nil
}

View File

@ -0,0 +1,298 @@
/*
Copyright 2017 The Kubernetes Authors.
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 importer
import (
"archive/tar"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strings"
"time"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/log"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/kubernetes-incubator/cri-containerd/pkg/util"
)
// This code reuses the docker import code from containerd/containerd#1602.
// It has been simplified a bit and garbage collection support was added.
// If a library/helper is added to containerd in the future, we should switch to it.
// manifestDotJSON is an entry in manifest.json.
type manifestDotJSON struct {
Config string
RepoTags []string
Layers []string
// Parent is unsupported
Parent string
}
// isLayerTar returns true if name is like "deadbeeddeadbeef/layer.tar"
func isLayerTar(name string) bool {
slashes := len(strings.Split(name, "/"))
return slashes == 2 && strings.HasSuffix(name, "/layer.tar")
}
// isDotJSON returns true if name is like "deadbeefdeadbeef.json"
func isDotJSON(name string) bool {
slashes := len(strings.Split(name, "/"))
return slashes == 1 && strings.HasSuffix(name, ".json")
}
type imageConfig struct {
desc ocispec.Descriptor
img ocispec.Image
}
// Import implements Docker Image Spec v1.1.
// An image MUST have `manifest.json`.
// `repositories` file in Docker Image Spec v1.0 is not supported (yet).
// Also, the current implementation assumes the implicit file name convention,
// which is not explicitly documented in the spec. (e.g. deadbeef/layer.tar)
// It returns a group of image references successfully loaded.
func Import(ctx context.Context, cs content.Store, is images.Store, reader io.Reader) (_ []string, retErr error) {
tr := tar.NewReader(reader)
var (
mfsts []manifestDotJSON
layers = make(map[string]ocispec.Descriptor) // key: filename (deadbeeddeadbeef/layer.tar)
configs = make(map[string]imageConfig) // key: filename (deadbeeddeadbeef.json)
)
// Either image is successfully imported or not, we should cleanup gc.root
// for all image layers.
defer func() {
for _, desc := range layers {
// Remove root tag from layers now that manifest refers to it
if _, err := cs.Update(ctx, content.Info{Digest: desc.Digest}, "labels.containerd.io/gc.root"); err != nil {
log.G(ctx).WithError(err).Error("Failed to remove layer %q root tag", desc.Digest)
}
}
for _, cfg := range configs {
// Remove root tag from config now that manifest refers to it
if _, err := cs.Update(ctx, content.Info{Digest: cfg.desc.Digest}, "labels.containerd.io/gc.root"); err != nil {
log.G(ctx).WithError(err).Error("Failed to remove config %q root tag", cfg.desc.Digest)
}
}
}()
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, errors.Wrap(err, "get next file")
}
if hdr.Typeflag != tar.TypeReg && hdr.Typeflag != tar.TypeRegA {
continue
}
if hdr.Name == "manifest.json" {
mfsts, err = onUntarManifestJSON(tr)
if err != nil {
return nil, errors.Wrapf(err, "untar manifest %q", hdr.Name)
}
continue
}
if isLayerTar(hdr.Name) {
desc, err := onUntarLayerTar(ctx, tr, cs, hdr.Name, hdr.Size)
if err != nil {
return nil, errors.Wrapf(err, "untar layer %q", hdr.Name)
}
layers[hdr.Name] = *desc
continue
}
if isDotJSON(hdr.Name) {
c, err := onUntarDotJSON(ctx, tr, cs, hdr.Name, hdr.Size)
if err != nil {
return nil, errors.Wrapf(err, "untar config %q", hdr.Name)
}
configs[hdr.Name] = *c
continue
}
}
var refs []string
defer func() {
if retErr == nil {
return
}
// TODO(random-liu): Consider whether we should keep images already imported
// even when there is an error.
for _, ref := range refs {
if err := is.Delete(ctx, ref); err != nil {
log.G(ctx).WithError(err).Errorf("Failed to remove image %q", ref)
}
}
}()
for _, mfst := range mfsts {
config, ok := configs[mfst.Config]
if !ok {
return refs, errors.Errorf("image config %q not found", mfst.Config)
}
schema2Manifest, err := makeDockerSchema2Manifest(mfst, config, layers)
if err != nil {
return refs, errors.Wrap(err, "create docker manifest")
}
desc, err := writeDockerSchema2Manifest(ctx, cs, *schema2Manifest, config.img.Architecture, config.img.OS)
if err != nil {
return refs, errors.Wrap(err, "write docker manifest")
}
defer func() {
// Remove root tag from manifest.
if _, err := cs.Update(ctx, content.Info{Digest: desc.Digest}, "labels.containerd.io/gc.root"); err != nil {
log.G(ctx).WithError(err).Error("Failed to remove manifest root tag")
}
}()
for _, ref := range mfst.RepoTags {
normalized, err := util.NormalizeImageRef(ref)
if err != nil {
return refs, errors.Wrapf(err, "normalize image ref %q", ref)
}
ref = normalized.String()
imgrec := images.Image{
Name: ref,
Target: *desc,
}
if _, err := is.Create(ctx, imgrec); err != nil {
if !errdefs.IsAlreadyExists(err) {
return refs, errors.Wrapf(err, "create image ref %+v", imgrec)
}
_, err := is.Update(ctx, imgrec)
if err != nil {
return refs, errors.Wrapf(err, "update image ref %+v", imgrec)
}
}
refs = append(refs, ref)
}
}
return refs, nil
}
func makeDockerSchema2Manifest(mfst manifestDotJSON, config imageConfig, layers map[string]ocispec.Descriptor) (*ocispec.Manifest, error) {
manifest := ocispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
Config: config.desc,
}
for _, f := range mfst.Layers {
desc, ok := layers[f]
if !ok {
return nil, errors.Errorf("layer %q not found", f)
}
manifest.Layers = append(manifest.Layers, desc)
}
return &manifest, nil
}
func writeDockerSchema2Manifest(ctx context.Context, cs content.Ingester, manifest ocispec.Manifest, arch, os string) (*ocispec.Descriptor, error) {
manifestBytes, err := json.Marshal(manifest)
if err != nil {
return nil, err
}
manifestBytesR := bytes.NewReader(manifestBytes)
manifestDigest := digest.FromBytes(manifestBytes)
labels := map[string]string{}
labels["containerd.io/gc.root"] = time.Now().UTC().Format(time.RFC3339)
labels["containerd.io/gc.ref.content.0"] = manifest.Config.Digest.String()
for i, ch := range manifest.Layers {
labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = ch.Digest.String()
}
if err := content.WriteBlob(ctx, cs, "manifest-"+manifestDigest.String(), manifestBytesR,
int64(len(manifestBytes)), manifestDigest, content.WithLabels(labels)); err != nil {
return nil, err
}
desc := &ocispec.Descriptor{
MediaType: images.MediaTypeDockerSchema2Manifest,
Digest: manifestDigest,
Size: int64(len(manifestBytes)),
}
if arch != "" || os != "" {
desc.Platform = &ocispec.Platform{
Architecture: arch,
OS: os,
}
}
return desc, nil
}
func onUntarManifestJSON(r io.Reader) ([]manifestDotJSON, error) {
// name: "manifest.json"
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
var mfsts []manifestDotJSON
if err := json.Unmarshal(b, &mfsts); err != nil {
return nil, err
}
return mfsts, nil
}
func onUntarLayerTar(ctx context.Context, r io.Reader, cs content.Ingester, name string, size int64) (*ocispec.Descriptor, error) {
// name is like "deadbeeddeadbeef/layer.tar" ( guaranteed by isLayerTar() )
split := strings.Split(name, "/")
// note: split[0] is not expected digest here
cw, err := cs.Writer(ctx, "layer-"+split[0], size, "")
if err != nil {
return nil, err
}
defer cw.Close()
if err := content.Copy(ctx, cw, r, size, "", content.WithLabels(map[string]string{
"containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339),
})); err != nil {
return nil, err
}
return &ocispec.Descriptor{
MediaType: images.MediaTypeDockerSchema2Layer,
Size: size,
Digest: cw.Digest(),
}, nil
}
func onUntarDotJSON(ctx context.Context, r io.Reader, cs content.Ingester, name string, size int64) (*imageConfig, error) {
config := imageConfig{}
config.desc.MediaType = images.MediaTypeDockerSchema2Config
config.desc.Size = size
// name is like "deadbeeddeadbeef.json" ( guaranteed by is DotJSON() )
split := strings.Split(name, ".")
cw, err := cs.Writer(ctx, "config-"+split[0], size, "")
if err != nil {
return nil, err
}
defer cw.Close()
var buf bytes.Buffer
tr := io.TeeReader(r, &buf)
if err := content.Copy(ctx, cw, tr, size, "", content.WithLabels(map[string]string{
"containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339),
})); err != nil {
return nil, err
}
config.desc.Digest = cw.Digest()
if err := json.Unmarshal(buf.Bytes(), &config.img); err != nil {
return nil, err
}
return &config, nil
}

View File

@ -43,7 +43,7 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
customopts "github.com/kubernetes-incubator/cri-containerd/pkg/opts" customopts "github.com/kubernetes-incubator/cri-containerd/pkg/containerd/opts"
cio "github.com/kubernetes-incubator/cri-containerd/pkg/server/io" cio "github.com/kubernetes-incubator/cri-containerd/pkg/server/io"
containerstore "github.com/kubernetes-incubator/cri-containerd/pkg/store/container" containerstore "github.com/kubernetes-incubator/cri-containerd/pkg/store/container"
"github.com/kubernetes-incubator/cri-containerd/pkg/util" "github.com/kubernetes-incubator/cri-containerd/pkg/util"

View File

@ -41,6 +41,7 @@ import (
"github.com/kubernetes-incubator/cri-containerd/pkg/store" "github.com/kubernetes-incubator/cri-containerd/pkg/store"
imagestore "github.com/kubernetes-incubator/cri-containerd/pkg/store/image" imagestore "github.com/kubernetes-incubator/cri-containerd/pkg/store/image"
"github.com/kubernetes-incubator/cri-containerd/pkg/util"
) )
const ( const (
@ -187,35 +188,6 @@ func criContainerStateToString(state runtime.ContainerState) string {
return runtime.ContainerState_name[int32(state)] return runtime.ContainerState_name[int32(state)]
} }
// normalizeImageRef normalizes the image reference following the docker convention. This is added
// mainly for backward compatibility.
// The reference returned can only be either tagged or digested. For reference contains both tag
// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
func normalizeImageRef(ref string) (reference.Named, error) {
named, err := reference.ParseNormalizedNamed(ref)
if err != nil {
return nil, err
}
if _, ok := named.(reference.NamedTagged); ok {
if canonical, ok := named.(reference.Canonical); ok {
// The reference is both tagged and digested, only
// return digested.
newNamed, err := reference.WithName(canonical.Name())
if err != nil {
return nil, err
}
newCanonical, err := reference.WithDigest(newNamed, canonical.Digest())
if err != nil {
return nil, err
}
return newCanonical, nil
}
}
return reference.TagNameOnly(named), nil
}
// getRepoDigestAngTag returns image repoDigest and repoTag of the named image reference. // getRepoDigestAngTag returns image repoDigest and repoTag of the named image reference.
func getRepoDigestAndTag(namedRef reference.Named, digest imagedigest.Digest, schema1 bool) (string, string) { func getRepoDigestAndTag(namedRef reference.Named, digest imagedigest.Digest, schema1 bool) (string, string) {
var repoTag, repoDigest string var repoTag, repoDigest string
@ -237,7 +209,7 @@ func (c *criContainerdService) localResolve(ctx context.Context, ref string) (*i
_, err := imagedigest.Parse(ref) _, err := imagedigest.Parse(ref)
if err != nil { if err != nil {
// ref is not image id, try to resolve it locally. // ref is not image id, try to resolve it locally.
normalized, err := normalizeImageRef(ref) normalized, err := util.NormalizeImageRef(ref)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid image reference %q: %v", ref, err) return nil, fmt.Errorf("invalid image reference %q: %v", ref, err)
} }

View File

@ -19,70 +19,11 @@ package server
import ( import (
"testing" "testing"
"github.com/containerd/containerd/reference"
imagedigest "github.com/opencontainers/go-digest" imagedigest "github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
)
func TestNormalizeImageRef(t *testing.T) { "github.com/kubernetes-incubator/cri-containerd/pkg/util"
for _, test := range []struct { )
input string
expect string
}{
{ // has nothing
input: "busybox",
expect: "docker.io/library/busybox:latest",
},
{ // only has tag
input: "busybox:latest",
expect: "docker.io/library/busybox:latest",
},
{ // only has digest
input: "busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
expect: "docker.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
},
{ // only has path
input: "library/busybox",
expect: "docker.io/library/busybox:latest",
},
{ // only has hostname
input: "docker.io/busybox",
expect: "docker.io/library/busybox:latest",
},
{ // has no tag
input: "docker.io/library/busybox",
expect: "docker.io/library/busybox:latest",
},
{ // has no path
input: "docker.io/busybox:latest",
expect: "docker.io/library/busybox:latest",
},
{ // has no hostname
input: "library/busybox:latest",
expect: "docker.io/library/busybox:latest",
},
{ // full reference
input: "docker.io/library/busybox:latest",
expect: "docker.io/library/busybox:latest",
},
{ // gcr reference
input: "gcr.io/library/busybox",
expect: "gcr.io/library/busybox:latest",
},
{ // both tag and digest
input: "gcr.io/library/busybox:latest@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
expect: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
},
} {
t.Logf("TestCase %q", test.input)
normalized, err := normalizeImageRef(test.input)
assert.NoError(t, err)
output := normalized.String()
assert.Equal(t, test.expect, output)
_, err = reference.Parse(output)
assert.NoError(t, err, "%q should be containerd supported reference", output)
}
}
// TestGetUserFromImage tests the logic of getting image uid or user name of image user. // TestGetUserFromImage tests the logic of getting image uid or user name of image user.
func TestGetUserFromImage(t *testing.T) { func TestGetUserFromImage(t *testing.T) {
@ -154,7 +95,7 @@ func TestGetRepoDigestAndTag(t *testing.T) {
}, },
} { } {
t.Logf("TestCase %q", desc) t.Logf("TestCase %q", desc)
named, err := normalizeImageRef(test.ref) named, err := util.NormalizeImageRef(test.ref)
assert.NoError(t, err) assert.NoError(t, err)
repoDigest, repoTag := getRepoDigestAndTag(named, digest, test.schema1) repoDigest, repoTag := getRepoDigestAndTag(named, digest, test.schema1)
assert.Equal(t, test.expectedRepoDigest, repoDigest) assert.Equal(t, test.expectedRepoDigest, repoDigest)

78
pkg/server/image_load.go Normal file
View File

@ -0,0 +1,78 @@
/*
Copyright 2017 The Kubernetes Authors.
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 server
import (
"fmt"
"golang.org/x/net/context"
"os"
"path/filepath"
"github.com/golang/glog"
api "github.com/kubernetes-incubator/cri-containerd/pkg/api/v1"
"github.com/kubernetes-incubator/cri-containerd/pkg/containerd/importer"
imagestore "github.com/kubernetes-incubator/cri-containerd/pkg/store/image"
)
// LoadImage loads a image into containerd.
func (c *criContainerdService) LoadImage(ctx context.Context, r *api.LoadImageRequest) (*api.LoadImageResponse, error) {
path := r.GetFilePath()
if !filepath.IsAbs(path) {
return nil, fmt.Errorf("path %q is not an absolute path", path)
}
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open file: %v", err)
}
repoTags, err := importer.Import(ctx, c.client.ContentStore(), c.client.ImageService(), f)
if err != nil {
return nil, fmt.Errorf("failed to import image: %v", err)
}
for _, repoTag := range repoTags {
image, err := c.client.GetImage(ctx, repoTag)
if err != nil {
return nil, fmt.Errorf("failed to get image %q: %v", repoTag, err)
}
if err := image.Unpack(ctx, c.config.ContainerdConfig.Snapshotter); err != nil {
glog.Warningf("Failed to unpack image %q: %v", repoTag, err)
// Do not fail image importing. Unpack will be retried when container creation.
}
info, err := getImageInfo(ctx, image, c.client.ContentStore())
if err != nil {
return nil, fmt.Errorf("failed to get image %q info: %v", repoTag, err)
}
id := info.id
if err := c.createImageReference(ctx, id, image.Target()); err != nil {
return nil, fmt.Errorf("failed to create image reference %q: %v", id, err)
}
img := imagestore.Image{
ID: id,
RepoTags: []string{repoTag},
ChainID: info.chainID.String(),
Size: info.size,
Config: &info.config,
Image: image,
}
if err := c.imageStore.Add(img); err != nil {
return nil, fmt.Errorf("failed to add image %q into store: %v", id, err)
}
glog.V(4).Infof("Imported image with id %q, repo tag %q", id, repoTag)
}
return &api.LoadImageResponse{Images: repoTags}, nil
}

View File

@ -32,6 +32,7 @@ import (
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
imagestore "github.com/kubernetes-incubator/cri-containerd/pkg/store/image" imagestore "github.com/kubernetes-incubator/cri-containerd/pkg/store/image"
"github.com/kubernetes-incubator/cri-containerd/pkg/util"
) )
// For image management: // For image management:
@ -75,7 +76,7 @@ import (
// PullImage pulls an image with authentication config. // PullImage pulls an image with authentication config.
func (c *criContainerdService) PullImage(ctx context.Context, r *runtime.PullImageRequest) (*runtime.PullImageResponse, error) { func (c *criContainerdService) PullImage(ctx context.Context, r *runtime.PullImageRequest) (*runtime.PullImageResponse, error) {
imageRef := r.GetImage().GetImage() imageRef := r.GetImage().GetImage()
namedRef, err := normalizeImageRef(imageRef) namedRef, err := util.NormalizeImageRef(imageRef)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse image reference %q: %v", imageRef, err) return nil, fmt.Errorf("failed to parse image reference %q: %v", imageRef, err)
} }

View File

@ -20,6 +20,8 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"golang.org/x/net/context" "golang.org/x/net/context"
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
api "github.com/kubernetes-incubator/cri-containerd/pkg/api/v1"
) )
// instrumentedService wraps service and logs each operation. // instrumentedService wraps service and logs each operation.
@ -316,3 +318,15 @@ func (in *instrumentedService) ListContainerStats(ctx context.Context, r *runtim
}() }()
return in.criContainerdService.ListContainerStats(ctx, r) return in.criContainerdService.ListContainerStats(ctx, r)
} }
func (in *instrumentedService) LoadImage(ctx context.Context, r *api.LoadImageRequest) (res *api.LoadImageResponse, err error) {
glog.V(4).Infof("LoadImage from file %q", r.GetFilePath())
defer func() {
if err != nil {
glog.Errorf("LoadImage failed, error: %v", err)
} else {
glog.V(4).Infof("LoadImage returns images %+v", res.GetImages())
}
}()
return in.criContainerdService.LoadImage(ctx, r)
}

View File

@ -33,7 +33,7 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
customopts "github.com/kubernetes-incubator/cri-containerd/pkg/opts" customopts "github.com/kubernetes-incubator/cri-containerd/pkg/containerd/opts"
sandboxstore "github.com/kubernetes-incubator/cri-containerd/pkg/store/sandbox" sandboxstore "github.com/kubernetes-incubator/cri-containerd/pkg/store/sandbox"
"github.com/kubernetes-incubator/cri-containerd/pkg/util" "github.com/kubernetes-incubator/cri-containerd/pkg/util"
) )

View File

@ -41,6 +41,7 @@ import (
"k8s.io/kubernetes/pkg/kubelet/server/streaming" "k8s.io/kubernetes/pkg/kubelet/server/streaming"
"github.com/kubernetes-incubator/cri-containerd/cmd/cri-containerd/options" "github.com/kubernetes-incubator/cri-containerd/cmd/cri-containerd/options"
api "github.com/kubernetes-incubator/cri-containerd/pkg/api/v1"
osinterface "github.com/kubernetes-incubator/cri-containerd/pkg/os" osinterface "github.com/kubernetes-incubator/cri-containerd/pkg/os"
"github.com/kubernetes-incubator/cri-containerd/pkg/registrar" "github.com/kubernetes-incubator/cri-containerd/pkg/registrar"
containerstore "github.com/kubernetes-incubator/cri-containerd/pkg/store/container" containerstore "github.com/kubernetes-incubator/cri-containerd/pkg/store/container"
@ -62,6 +63,7 @@ type CRIContainerdService interface {
Stop() Stop()
runtime.RuntimeServiceServer runtime.RuntimeServiceServer
runtime.ImageServiceServer runtime.ImageServiceServer
api.CRIContainerdServiceServer
} }
// criContainerdService implements CRIContainerdService. // criContainerdService implements CRIContainerdService.
@ -167,8 +169,10 @@ func NewCRIContainerdService(config options.Config) (CRIContainerdService, error
// Create the grpc server and register runtime and image services. // Create the grpc server and register runtime and image services.
c.server = grpc.NewServer() c.server = grpc.NewServer()
runtime.RegisterRuntimeServiceServer(c.server, newInstrumentedService(c)) instrumented := newInstrumentedService(c)
runtime.RegisterImageServiceServer(c.server, newInstrumentedService(c)) runtime.RegisterRuntimeServiceServer(c.server, instrumented)
runtime.RegisterImageServiceServer(c.server, instrumented)
api.RegisterCRIContainerdServiceServer(c.server, instrumented)
return newInstrumentedService(c), nil return newInstrumentedService(c), nil
} }

50
pkg/util/image.go Normal file
View File

@ -0,0 +1,50 @@
/*
Copyright 2017 The Kubernetes Authors.
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 util
import (
"github.com/docker/distribution/reference"
)
// NormalizeImageRef normalizes the image reference following the docker convention. This is added
// mainly for backward compatibility.
// The reference returned can only be either tagged or digested. For reference contains both tag
// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
func NormalizeImageRef(ref string) (reference.Named, error) {
named, err := reference.ParseNormalizedNamed(ref)
if err != nil {
return nil, err
}
if _, ok := named.(reference.NamedTagged); ok {
if canonical, ok := named.(reference.Canonical); ok {
// The reference is both tagged and digested, only
// return digested.
newNamed, err := reference.WithName(canonical.Name())
if err != nil {
return nil, err
}
newCanonical, err := reference.WithDigest(newNamed, canonical.Digest())
if err != nil {
return nil, err
}
return newCanonical, nil
}
}
return reference.TagNameOnly(named), nil
}

84
pkg/util/image_test.go Normal file
View File

@ -0,0 +1,84 @@
/*
Copyright 2017 The Kubernetes Authors.
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 util
import (
"testing"
"github.com/containerd/containerd/reference"
"github.com/stretchr/testify/assert"
)
func TestNormalizeImageRef(t *testing.T) {
for _, test := range []struct {
input string
expect string
}{
{ // has nothing
input: "busybox",
expect: "docker.io/library/busybox:latest",
},
{ // only has tag
input: "busybox:latest",
expect: "docker.io/library/busybox:latest",
},
{ // only has digest
input: "busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
expect: "docker.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
},
{ // only has path
input: "library/busybox",
expect: "docker.io/library/busybox:latest",
},
{ // only has hostname
input: "docker.io/busybox",
expect: "docker.io/library/busybox:latest",
},
{ // has no tag
input: "docker.io/library/busybox",
expect: "docker.io/library/busybox:latest",
},
{ // has no path
input: "docker.io/busybox:latest",
expect: "docker.io/library/busybox:latest",
},
{ // has no hostname
input: "library/busybox:latest",
expect: "docker.io/library/busybox:latest",
},
{ // full reference
input: "docker.io/library/busybox:latest",
expect: "docker.io/library/busybox:latest",
},
{ // gcr reference
input: "gcr.io/library/busybox",
expect: "gcr.io/library/busybox:latest",
},
{ // both tag and digest
input: "gcr.io/library/busybox:latest@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
expect: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
},
} {
t.Logf("TestCase %q", test.input)
normalized, err := NormalizeImageRef(test.input)
assert.NoError(t, err)
output := normalized.String()
assert.Equal(t, test.expect, output)
_, err = reference.Parse(output)
assert.NoError(t, err, "%q should be containerd supported reference", output)
}
}