Merge pull request #963 from stevvooe/namespaces-support

namespaces: support within containerd
This commit is contained in:
Michael Crosby 2017-06-06 14:45:25 -07:00 committed by GitHub
commit bdf9f5f738
52 changed files with 1184 additions and 223 deletions

View File

@ -52,7 +52,7 @@ var _ = math.Inf
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type Namespace struct {
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// Labels provides an area to include arbitrary data on namespaces.
//
// Note that to add a new value to this field, read the existing set and
@ -124,6 +124,10 @@ type UpdateNamespaceRequest struct {
Namespace Namespace `protobuf:"bytes,1,opt,name=namespace" json:"namespace"`
// UpdateMask specifies which fields to perform the update on. If empty,
// the operation applies to all fields.
//
// For the most part, this applies only to selectively updating labels on
// the namespace. While field masks are typically limited to ascii alphas
// and digits, we just take everything after the "labels." as the map key.
UpdateMask *google_protobuf2.FieldMask `protobuf:"bytes,2,opt,name=update_mask,json=updateMask" json:"update_mask,omitempty"`
}
@ -140,7 +144,7 @@ func (*UpdateNamespaceResponse) ProtoMessage() {}
func (*UpdateNamespaceResponse) Descriptor() ([]byte, []int) { return fileDescriptorNamespace, []int{8} }
type DeleteNamespaceRequest struct {
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (m *DeleteNamespaceRequest) Reset() { *m = DeleteNamespaceRequest{} }
@ -379,11 +383,11 @@ func (m *Namespace) MarshalTo(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.Namespace) > 0 {
if len(m.Name) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintNamespace(dAtA, i, uint64(len(m.Namespace)))
i += copy(dAtA[i:], m.Namespace)
i = encodeVarintNamespace(dAtA, i, uint64(len(m.Name)))
i += copy(dAtA[i:], m.Name)
}
if len(m.Labels) > 0 {
for k, _ := range m.Labels {
@ -638,11 +642,11 @@ func (m *DeleteNamespaceRequest) MarshalTo(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.Namespace) > 0 {
if len(m.Name) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintNamespace(dAtA, i, uint64(len(m.Namespace)))
i += copy(dAtA[i:], m.Namespace)
i = encodeVarintNamespace(dAtA, i, uint64(len(m.Name)))
i += copy(dAtA[i:], m.Name)
}
return i, nil
}
@ -677,7 +681,7 @@ func encodeVarintNamespace(dAtA []byte, offset int, v uint64) int {
func (m *Namespace) Size() (n int) {
var l int
_ = l
l = len(m.Namespace)
l = len(m.Name)
if l > 0 {
n += 1 + l + sovNamespace(uint64(l))
}
@ -771,7 +775,7 @@ func (m *UpdateNamespaceResponse) Size() (n int) {
func (m *DeleteNamespaceRequest) Size() (n int) {
var l int
_ = l
l = len(m.Namespace)
l = len(m.Name)
if l > 0 {
n += 1 + l + sovNamespace(uint64(l))
}
@ -806,7 +810,7 @@ func (this *Namespace) String() string {
}
mapStringForLabels += "}"
s := strings.Join([]string{`&Namespace{`,
`Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`,
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
`Labels:` + mapStringForLabels + `,`,
`}`,
}, "")
@ -898,7 +902,7 @@ func (this *DeleteNamespaceRequest) String() string {
return "nil"
}
s := strings.Join([]string{`&DeleteNamespaceRequest{`,
`Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`,
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
`}`,
}, "")
return s
@ -942,7 +946,7 @@ func (m *Namespace) Unmarshal(dAtA []byte) error {
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType)
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
@ -967,7 +971,7 @@ func (m *Namespace) Unmarshal(dAtA []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Namespace = string(dAtA[iNdEx:postIndex])
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
@ -1809,7 +1813,7 @@ func (m *DeleteNamespaceRequest) Unmarshal(dAtA []byte) error {
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType)
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
@ -1834,7 +1838,7 @@ func (m *DeleteNamespaceRequest) Unmarshal(dAtA []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Namespace = string(dAtA[iNdEx:postIndex])
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
@ -1967,39 +1971,38 @@ func init() {
}
var fileDescriptorNamespace = []byte{
// 533 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xcd, 0x8e, 0xd3, 0x30,
0x10, 0xae, 0xdb, 0x52, 0xa9, 0x93, 0x0b, 0x32, 0x25, 0x44, 0x01, 0x85, 0x2a, 0x5c, 0x96, 0x03,
0x0e, 0x5b, 0x24, 0xc4, 0xcf, 0x6d, 0x61, 0x29, 0x48, 0x0b, 0x87, 0x48, 0x9c, 0x57, 0x4e, 0xeb,
0x86, 0xa8, 0xf9, 0x23, 0x76, 0x2a, 0xf5, 0xc6, 0x1b, 0xf0, 0x06, 0xbc, 0x01, 0xef, 0xd1, 0x23,
0x47, 0x4e, 0x88, 0xed, 0x93, 0xa0, 0x38, 0x69, 0xd3, 0x6d, 0xd3, 0xa8, 0x2b, 0x95, 0xdb, 0xd8,
0x9e, 0xcf, 0xdf, 0xcc, 0xf8, 0xfb, 0x12, 0x78, 0xef, 0x7a, 0xe2, 0x4b, 0xea, 0x90, 0x51, 0x14,
0x58, 0xa3, 0x28, 0x14, 0xd4, 0x0b, 0x59, 0x32, 0xde, 0x0c, 0x69, 0xec, 0x59, 0x9c, 0x25, 0x33,
0x6f, 0xc4, 0xb8, 0x15, 0xd2, 0x80, 0xf1, 0x98, 0x5e, 0x0b, 0x49, 0x9c, 0x44, 0x22, 0xc2, 0x5a,
0x89, 0x21, 0xb3, 0x53, 0x52, 0x66, 0xea, 0x3d, 0x37, 0x72, 0x23, 0x99, 0x64, 0x65, 0x51, 0x9e,
0xaf, 0xdf, 0x77, 0xa3, 0xc8, 0xf5, 0x99, 0x25, 0x57, 0x4e, 0x3a, 0xb1, 0x58, 0x10, 0x8b, 0x79,
0x71, 0xd8, 0xdf, 0x3e, 0x9c, 0x78, 0xcc, 0x1f, 0x5f, 0x06, 0x94, 0x4f, 0xf3, 0x0c, 0xf3, 0x27,
0x82, 0xee, 0xa7, 0x15, 0x07, 0x7e, 0x00, 0xdd, 0x35, 0xa1, 0x86, 0xfa, 0xe8, 0xa4, 0x6b, 0x97,
0x1b, 0x78, 0x08, 0x1d, 0x9f, 0x3a, 0xcc, 0xe7, 0x5a, 0xb3, 0xdf, 0x3a, 0x51, 0x06, 0x16, 0xd9,
0x57, 0x2b, 0x59, 0x5f, 0x49, 0x2e, 0x24, 0xe2, 0x3c, 0x14, 0xc9, 0xdc, 0x2e, 0xe0, 0xfa, 0x4b,
0x50, 0x36, 0xb6, 0xf1, 0x6d, 0x68, 0x4d, 0xd9, 0xbc, 0xe0, 0xcb, 0x42, 0xdc, 0x83, 0x5b, 0x33,
0xea, 0xa7, 0x4c, 0x6b, 0xca, 0xbd, 0x7c, 0xf1, 0xaa, 0xf9, 0x02, 0x99, 0x8f, 0xe1, 0xce, 0x90,
0x89, 0xf5, 0xf5, 0x36, 0xfb, 0x9a, 0x32, 0x2e, 0x30, 0x86, 0x76, 0xc6, 0x5e, 0xdc, 0x21, 0x63,
0xf3, 0x12, 0x7a, 0xd7, 0x53, 0x79, 0x1c, 0x85, 0x3c, 0x6b, 0x63, 0xab, 0x49, 0x65, 0xf0, 0xe8,
0x80, 0x4e, 0xce, 0xda, 0x8b, 0x3f, 0x0f, 0x1b, 0x1b, 0xf3, 0x30, 0x2d, 0xb8, 0x7b, 0xe1, 0xf1,
0x92, 0x81, 0xaf, 0xaa, 0x51, 0xa1, 0x33, 0xf1, 0x7c, 0xc1, 0x92, 0xa2, 0x9e, 0x62, 0x65, 0x8e,
0x40, 0xdd, 0x06, 0x14, 0x35, 0x7d, 0x00, 0x28, 0x39, 0x35, 0x24, 0xc7, 0x7b, 0x83, 0xa2, 0x36,
0xc0, 0x26, 0x05, 0xf5, 0x4d, 0xc2, 0xa8, 0x60, 0x3b, 0x43, 0x3a, 0x5a, 0xe3, 0x0e, 0xdc, 0xdb,
0xa1, 0x38, 0xf6, 0x70, 0x7f, 0x20, 0x50, 0x3f, 0xc7, 0xe3, 0xff, 0xd9, 0x07, 0x7e, 0x0d, 0x4a,
0x2a, 0x29, 0xa4, 0x23, 0xa4, 0xd8, 0x94, 0x81, 0x4e, 0x72, 0xd3, 0x90, 0x95, 0x69, 0xc8, 0xbb,
0xcc, 0x34, 0x1f, 0x29, 0x9f, 0xda, 0x90, 0xa7, 0x67, 0x71, 0x36, 0x84, 0x9d, 0xfa, 0x8e, 0x3d,
0x84, 0xe7, 0xa0, 0xbe, 0x65, 0x3e, 0xab, 0x98, 0x41, 0xad, 0x53, 0x07, 0xdf, 0xdb, 0x00, 0xa5,
0xca, 0xf0, 0x18, 0x5a, 0x43, 0x26, 0xf0, 0x93, 0xfd, 0x35, 0x54, 0x78, 0x4a, 0x27, 0x87, 0xa6,
0x17, 0x5d, 0x7b, 0xd0, 0xce, 0xd4, 0x8d, 0x6b, 0x3e, 0x0b, 0x95, 0x76, 0xd1, 0x9f, 0x1e, 0x0e,
0x28, 0xa8, 0x02, 0xe8, 0xe4, 0x02, 0xc4, 0x35, 0xd8, 0x6a, 0x17, 0xe8, 0xa7, 0x37, 0x40, 0x94,
0x74, 0xf9, 0x53, 0xd7, 0xd1, 0x55, 0x8b, 0xb5, 0x8e, 0x6e, 0x9f, 0x7c, 0x6c, 0xe8, 0xe4, 0xaf,
0x5e, 0x47, 0x57, 0xad, 0x0b, 0x5d, 0xdd, 0x51, 0xef, 0x79, 0xf6, 0x3f, 0x38, 0xd3, 0x16, 0x57,
0x46, 0xe3, 0xf7, 0x95, 0xd1, 0xf8, 0xb6, 0x34, 0xd0, 0x62, 0x69, 0xa0, 0x5f, 0x4b, 0x03, 0xfd,
0x5d, 0x1a, 0xc8, 0xe9, 0xc8, 0xcc, 0x67, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd8, 0xf7, 0xf8,
0x92, 0xc3, 0x06, 0x00, 0x00,
// 528 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x54, 0xbd, 0x8e, 0xd3, 0x4c,
0x14, 0xcd, 0x24, 0xf9, 0x2c, 0xe5, 0xba, 0xf9, 0x34, 0x04, 0x63, 0x19, 0xc9, 0x44, 0xa6, 0x59,
0x24, 0x18, 0xb3, 0xa1, 0xe1, 0xa7, 0x5b, 0x58, 0x02, 0xd2, 0x42, 0x61, 0x89, 0x7a, 0x35, 0x4e,
0x26, 0xc6, 0x8a, 0xff, 0xf0, 0x8c, 0x23, 0xa5, 0xe3, 0x0d, 0x78, 0x03, 0x1a, 0x5e, 0x26, 0x25,
0x25, 0x15, 0x62, 0xf3, 0x24, 0xc8, 0x63, 0x27, 0xce, 0x6e, 0x1c, 0x2b, 0x2b, 0x85, 0xee, 0x8e,
0x7d, 0x8e, 0xcf, 0xb9, 0xd7, 0xe7, 0x0e, 0xbc, 0xf3, 0x7c, 0xf1, 0x39, 0x73, 0xc9, 0x38, 0x0e,
0xed, 0x71, 0x1c, 0x09, 0xea, 0x47, 0x2c, 0x9d, 0x6c, 0x97, 0x34, 0xf1, 0x6d, 0xce, 0xd2, 0xb9,
0x3f, 0x66, 0xdc, 0x8e, 0x68, 0xc8, 0x78, 0x42, 0xaf, 0x95, 0x24, 0x49, 0x63, 0x11, 0x63, 0xbd,
0xe2, 0x90, 0xf9, 0x29, 0xa9, 0x90, 0x46, 0xdf, 0x8b, 0xbd, 0x58, 0x82, 0xec, 0xbc, 0x2a, 0xf0,
0xc6, 0x7d, 0x2f, 0x8e, 0xbd, 0x80, 0xd9, 0xf2, 0xe4, 0x66, 0x53, 0x9b, 0x85, 0x89, 0x58, 0x94,
0x2f, 0x07, 0x37, 0x5f, 0x4e, 0x7d, 0x16, 0x4c, 0x2e, 0x43, 0xca, 0x67, 0x05, 0xc2, 0xfa, 0x81,
0xa0, 0xf7, 0x71, 0xad, 0x81, 0x31, 0x74, 0x73, 0x41, 0x1d, 0x0d, 0xd0, 0x49, 0xcf, 0x91, 0x35,
0x1e, 0x81, 0x12, 0x50, 0x97, 0x05, 0x5c, 0x6f, 0x0f, 0x3a, 0x27, 0xea, 0xd0, 0x26, 0xfb, 0x1c,
0x92, 0xcd, 0x87, 0xc8, 0x85, 0x64, 0x9c, 0x47, 0x22, 0x5d, 0x38, 0x25, 0xdd, 0x78, 0x01, 0xea,
0xd6, 0x63, 0xfc, 0x3f, 0x74, 0x66, 0x6c, 0x51, 0x4a, 0xe5, 0x25, 0xee, 0xc3, 0x7f, 0x73, 0x1a,
0x64, 0x4c, 0x6f, 0xcb, 0x67, 0xc5, 0xe1, 0x65, 0xfb, 0x39, 0xb2, 0x1e, 0xc1, 0x9d, 0x11, 0x13,
0x9b, 0xcf, 0x3b, 0xec, 0x4b, 0xc6, 0xb8, 0xa8, 0xb3, 0x6b, 0x5d, 0x42, 0xff, 0x3a, 0x94, 0x27,
0x71, 0xc4, 0xf3, 0x36, 0x7a, 0x1b, 0xa7, 0x92, 0xa0, 0x0e, 0x1f, 0x1e, 0xd0, 0xc9, 0x59, 0x77,
0xf9, 0xfb, 0x41, 0xcb, 0xa9, 0xb8, 0x96, 0x0d, 0x77, 0x2f, 0x7c, 0x5e, 0x29, 0xf0, 0xb5, 0x1b,
0x0d, 0x94, 0xa9, 0x1f, 0x08, 0x96, 0x96, 0x7e, 0xca, 0x93, 0x35, 0x06, 0xed, 0x26, 0xa1, 0xf4,
0xf4, 0x1e, 0xa0, 0xd2, 0xd4, 0x91, 0x1c, 0xef, 0x2d, 0x4c, 0x6d, 0x91, 0x2d, 0x0a, 0xda, 0xeb,
0x94, 0x51, 0xc1, 0x76, 0x86, 0x74, 0xb4, 0xc6, 0x5d, 0xb8, 0xb7, 0x23, 0x71, 0xec, 0xe1, 0x7e,
0x47, 0xa0, 0x7d, 0x4a, 0x26, 0xff, 0xb2, 0x0f, 0xfc, 0x0a, 0xd4, 0x4c, 0x4a, 0xc8, 0x3d, 0x90,
0x61, 0x53, 0x87, 0x06, 0x29, 0x56, 0x85, 0xac, 0x57, 0x85, 0xbc, 0xcd, 0x57, 0xe5, 0x03, 0xe5,
0x33, 0x07, 0x0a, 0x78, 0x5e, 0xe7, 0x43, 0xd8, 0xf1, 0x77, 0xec, 0x21, 0x3c, 0x06, 0xed, 0x0d,
0x0b, 0x58, 0xcd, 0x0c, 0x6a, 0x02, 0x3f, 0xfc, 0xd6, 0x05, 0xa8, 0xb2, 0x85, 0x27, 0xd0, 0x19,
0x31, 0x81, 0x9f, 0xec, 0x57, 0xae, 0xd9, 0x24, 0x83, 0x1c, 0x0a, 0x2f, 0x7b, 0xf5, 0xa1, 0x9b,
0x67, 0x1a, 0x37, 0x5c, 0x06, 0xb5, 0x4b, 0x62, 0x3c, 0x3d, 0x9c, 0x50, 0x4a, 0x85, 0xa0, 0x14,
0xb1, 0xc3, 0x0d, 0xdc, 0xfa, 0xec, 0x1b, 0xa7, 0xb7, 0x60, 0x54, 0x72, 0xc5, 0x0f, 0x6e, 0x92,
0xab, 0x8f, 0x68, 0x93, 0xdc, 0xbe, 0xd0, 0x38, 0xa0, 0x14, 0xff, 0xba, 0x49, 0xae, 0x3e, 0x0d,
0x86, 0xb6, 0x93, 0xd9, 0xf3, 0xfc, 0xee, 0x3f, 0xd3, 0x97, 0x57, 0x66, 0xeb, 0xd7, 0x95, 0xd9,
0xfa, 0xba, 0x32, 0xd1, 0x72, 0x65, 0xa2, 0x9f, 0x2b, 0x13, 0xfd, 0x59, 0x99, 0xc8, 0x55, 0x24,
0xf2, 0xd9, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x2e, 0xc3, 0x29, 0xaf, 0x06, 0x00, 0x00,
}

View File

@ -26,7 +26,7 @@ service Namespaces {
}
message Namespace {
string namespace = 1;
string name = 1;
// Labels provides an area to include arbitrary data on namespaces.
//
@ -72,6 +72,10 @@ message UpdateNamespaceRequest {
// UpdateMask specifies which fields to perform the update on. If empty,
// the operation applies to all fields.
//
// For the most part, this applies only to selectively updating labels on
// the namespace. While field masks are typically limited to ascii alphas
// and digits, we just take everything after the "labels." as the map key.
google.protobuf.FieldMask update_mask = 2;
}
@ -80,5 +84,5 @@ message UpdateNamespaceResponse {
}
message DeleteNamespaceRequest {
string namespace = 1;
string name = 1;
}

View File

@ -1,7 +1,6 @@
package containerd
import (
"context"
"syscall"
"testing"
)
@ -20,9 +19,11 @@ func TestCheckpointRestore(t *testing.T) {
defer client.Close()
var (
ctx = context.Background()
id = "CheckpointRestore"
ctx, cancel = testContext()
id = "CheckpointRestore"
)
defer cancel()
image, err := client.GetImage(ctx, testImage)
if err != nil {
t.Error(err)
@ -113,7 +114,9 @@ func TestCheckpointRestoreNewContainer(t *testing.T) {
defer client.Close()
const id = "CheckpointRestoreNewContainer"
ctx := context.Background()
ctx, cancel := testContext()
defer cancel()
image, err := client.GetImage(ctx, testImage)
if err != nil {
t.Error(err)

View File

@ -15,6 +15,7 @@ import (
diffapi "github.com/containerd/containerd/api/services/diff"
"github.com/containerd/containerd/api/services/execution"
imagesapi "github.com/containerd/containerd/api/services/images"
namespacesapi "github.com/containerd/containerd/api/services/namespaces"
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
@ -43,13 +44,6 @@ func init() {
type NewClientOpts func(c *Client) error
func WithNamespace(namespace string) NewClientOpts {
return func(c *Client) error {
c.namespace = namespace
return nil
}
}
// New returns a new containerd client that is connected to the containerd
// instance provided by address
func New(address string, opts ...NewClientOpts) (*Client, error) {
@ -79,8 +73,7 @@ func New(address string, opts ...NewClientOpts) (*Client, error) {
type Client struct {
conn *grpc.ClientConn
runtime string
namespace string
runtime string
}
func (c *Client) IsServing(ctx context.Context) (bool, error) {
@ -438,6 +431,10 @@ func (c *Client) Close() error {
return c.conn.Close()
}
func (c *Client) NamespaceService() namespacesapi.NamespacesClient {
return namespacesapi.NewNamespacesClient(c.conn)
}
func (c *Client) ContainerService() containers.ContainersClient {
return containers.NewContainersClient(c.conn)
}

View File

@ -10,6 +10,8 @@ import (
"syscall"
"testing"
"time"
"github.com/containerd/containerd/namespaces"
)
const (
@ -30,6 +32,12 @@ func init() {
flag.Parse()
}
func testContext() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
ctx = namespaces.WithNamespace(ctx, "testing")
return ctx, cancel
}
func TestMain(m *testing.M) {
if testing.Short() {
os.Exit(m.Run())
@ -40,9 +48,12 @@ func TestMain(m *testing.M) {
supportsCriu = err == nil
var (
cmd *exec.Cmd
buf = bytes.NewBuffer(nil)
cmd *exec.Cmd
buf = bytes.NewBuffer(nil)
ctx, cancel = testContext()
)
defer cancel()
if !noDaemon {
// setup a new containerd daemon if !testing.Short
cmd = exec.Command("containerd",
@ -61,12 +72,13 @@ func TestMain(m *testing.M) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := waitForDaemonStart(client); err != nil {
if err := waitForDaemonStart(ctx, client); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// pull a seed image
if _, err = client.Pull(context.Background(), testImage, WithPullUnpack); err != nil {
if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
@ -98,13 +110,14 @@ func TestMain(m *testing.M) {
os.Exit(status)
}
func waitForDaemonStart(client *Client) error {
func waitForDaemonStart(ctx context.Context, client *Client) error {
var (
serving bool
err error
)
for i := 0; i < 20; i++ {
serving, err = client.IsServing(context.Background())
serving, err = client.IsServing(ctx)
if serving {
return nil
}
@ -133,13 +146,16 @@ func TestImagePull(t *testing.T) {
if testing.Short() {
t.Skip()
}
ctx, cancel := testContext()
defer cancel()
client, err := New(address)
if err != nil {
t.Fatal(err)
}
defer client.Close()
_, err = client.Pull(context.Background(), testImage)
_, err = client.Pull(ctx, testImage)
if err != nil {
t.Error(err)
return

View File

@ -10,6 +10,7 @@ import (
_ "github.com/containerd/containerd/services/healthcheck"
_ "github.com/containerd/containerd/services/images"
_ "github.com/containerd/containerd/services/metrics"
_ "github.com/containerd/containerd/services/namespaces"
_ "github.com/containerd/containerd/services/snapshot"
_ "github.com/containerd/containerd/services/version"
)

View File

@ -23,11 +23,11 @@ import (
diffapi "github.com/containerd/containerd/api/services/diff"
api "github.com/containerd/containerd/api/services/execution"
imagesapi "github.com/containerd/containerd/api/services/images"
namespacesapi "github.com/containerd/containerd/api/services/namespaces"
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
versionapi "github.com/containerd/containerd/api/services/version"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/metadata"
"github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/snapshot"
"github.com/containerd/containerd/sys"
@ -265,10 +265,6 @@ func resolveMetaDB(ctx *cli.Context) (*bolt.DB, error) {
return nil, err
}
if err := metadata.InitDB(db); err != nil {
return nil, err
}
return db, nil
}
@ -474,6 +470,8 @@ func interceptor(ctx gocontext.Context,
ctx = log.WithModule(ctx, "snapshot")
case diffapi.DiffServer:
ctx = log.WithModule(ctx, "diff")
case namespacesapi.NamespacesServer:
ctx = log.WithModule(ctx, "namespaces")
default:
log.G(ctx).Warnf("unknown GRPC server type: %#v\n", info.Server)
}

View File

@ -40,9 +40,11 @@ var checkpointCommand = cli.Command{
},
Action: func(context *cli.Context) error {
var (
id = context.String("id")
ctx = gocontext.Background()
id = context.String("id")
ctx, cancel = appContext(context)
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}

View File

@ -1,7 +1,6 @@
package main
import (
gocontext "context"
"runtime"
"google.golang.org/grpc"
@ -18,6 +17,9 @@ var deleteCommand = cli.Command{
Usage: "delete an existing container",
ArgsUsage: "CONTAINER",
Action: func(context *cli.Context) error {
ctx, cancel := appContext(context)
defer cancel()
containers, err := getContainersService(context)
if err != nil {
return err
@ -35,7 +37,7 @@ var deleteCommand = cli.Command{
if id == "" {
return errors.New("container id must be provided")
}
ctx := gocontext.TODO()
_, err = containers.Delete(ctx, &containersapi.DeleteContainerRequest{
ID: id,
})

View File

@ -1,7 +1,6 @@
package main
import (
gocontext "context"
"fmt"
"os"
"text/tabwriter"
@ -14,11 +13,14 @@ var eventsCommand = cli.Command{
Name: "events",
Usage: "display containerd events",
Action: func(context *cli.Context) error {
ctx, cancel := appContext(context)
defer cancel()
tasks, err := getTasksService(context)
if err != nil {
return err
}
events, err := tasks.Events(gocontext.Background(), &execution.EventsRequest{})
events, err := tasks.Events(ctx, &execution.EventsRequest{})
if err != nil {
return err
}

View File

@ -1,7 +1,6 @@
package main
import (
gocontext "context"
"os"
"github.com/Sirupsen/logrus"
@ -30,9 +29,11 @@ var execCommand = cli.Command{
},
Action: func(context *cli.Context) error {
var (
id = context.String("id")
ctx = gocontext.Background()
id = context.String("id")
ctx, cancel = appContext(context)
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}

View File

@ -22,7 +22,12 @@ var infoCommand = cli.Command{
},
},
Action: func(context *cli.Context) error {
id := context.String("id")
var (
id = context.String("id")
ctx, cancel = appContext(context)
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}
@ -36,7 +41,7 @@ var infoCommand = cli.Command{
return err
}
containerResponse, err := containers.Get(gocontext.TODO(), &containersapi.GetContainerRequest{ID: id})
containerResponse, err := containers.Get(ctx, &containersapi.GetContainerRequest{ID: id})
if err != nil {
return err
}

View File

@ -1,8 +1,6 @@
package main
import (
gocontext "context"
"github.com/containerd/containerd/api/services/execution"
"github.com/pkg/errors"
"github.com/urfave/cli"
@ -27,7 +25,12 @@ var killCommand = cli.Command{
},
},
Action: func(context *cli.Context) error {
id := context.String("id")
var (
id = context.String("id")
ctx, cancel = appContext(context)
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}
@ -66,7 +69,7 @@ var killCommand = cli.Command{
if err != nil {
return err
}
_, err = tasks.Kill(gocontext.Background(), killRequest)
_, err = tasks.Kill(ctx, killRequest)
if err != nil {
return err
}

View File

@ -27,7 +27,11 @@ var listCommand = cli.Command{
},
},
Action: func(context *cli.Context) error {
quiet := context.Bool("quiet")
var (
quiet = context.Bool("quiet")
ctx, cancel = appContext(context)
)
defer cancel()
tasks, err := getTasksService(context)
if err != nil {
@ -50,7 +54,7 @@ var listCommand = cli.Command{
}
} else {
tasksResponse, err := tasks.List(gocontext.TODO(), &execution.ListRequest{})
tasksResponse, err := tasks.List(ctx, &execution.ListRequest{})
if err != nil {
return err
}

View File

@ -42,12 +42,15 @@ containerd client
Usage: "address for containerd's GRPC server",
Value: "/run/containerd/containerd.sock",
},
cli.DurationFlag{
Name: "timeout",
Usage: "total timeout for ctr commands",
},
cli.StringFlag{
// TODO(stevvooe): for now, we allow circumventing the GRPC. Once
// we have clear separation, this will likely go away.
Name: "root",
Usage: "path to content store root",
Value: "/var/lib/containerd",
Name: "namespace, n",
Usage: "namespace to use with commands",
Value: "default",
EnvVar: "CONTAINERD_NAMESPACE",
},
}
app.Commands = []cli.Command{
@ -55,6 +58,7 @@ containerd client
runCommand,
eventsCommand,
deleteCommand,
namespacesCommand,
listCommand,
infoCommand,
killCommand,

201
cmd/ctr/namespaces.go Normal file
View File

@ -0,0 +1,201 @@
package main
import (
"context"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/metadata"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var namespacesCommand = cli.Command{
Name: "namespaces",
Usage: "manage namespaces",
Subcommands: cli.Commands{
namespacesCreateCommand,
namespacesSetLabelsCommand,
namespacesListCommand,
namespacesRemoveCommand,
},
}
var namespacesCreateCommand = cli.Command{
Name: "create",
Usage: "Create a new namespace.",
ArgsUsage: "[flags] <name> [<key>=<value]",
Description: "Create a new namespace. It must be unique.",
Action: func(clicontext *cli.Context) error {
var (
ctx = context.Background()
namespace, labels = namespaceWithLabelArgs(clicontext)
)
if namespace == "" {
return errors.New("please specify a namespace")
}
namespaces, err := getNamespacesService(clicontext)
if err != nil {
return err
}
if err := namespaces.Create(ctx, namespace, labels); err != nil {
return err
}
return nil
},
}
func namespaceWithLabelArgs(clicontext *cli.Context) (string, map[string]string) {
var (
namespace = clicontext.Args().First()
labelStrings = clicontext.Args().Tail()
labels = make(map[string]string, len(labelStrings))
)
for _, label := range labelStrings {
parts := strings.SplitN(label, "=", 2)
key := parts[0]
value := "true"
if len(parts) > 1 {
value = parts[1]
}
labels[key] = value
}
return namespace, labels
}
var namespacesSetLabelsCommand = cli.Command{
Name: "set-labels",
Usage: "Set and clear labels for a namespace.",
ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
Description: "Set and clear labels for a namespace.",
Flags: []cli.Flag{},
Action: func(clicontext *cli.Context) error {
var (
ctx = context.Background()
namespace, labels = namespaceWithLabelArgs(clicontext)
)
namespaces, err := getNamespacesService(clicontext)
if err != nil {
return err
}
if namespace == "" {
return errors.New("please specify a namespace")
}
for k, v := range labels {
if err := namespaces.SetLabel(ctx, namespace, k, v); err != nil {
return err
}
}
return nil
},
}
var namespacesListCommand = cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "List namespaces.",
ArgsUsage: "[flags]",
Description: "List namespaces.",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "quiet, q",
Usage: "print only the namespace name.",
},
},
Action: func(clicontext *cli.Context) error {
var (
ctx = context.Background()
quiet = clicontext.Bool("quiet")
)
namespaces, err := getNamespacesService(clicontext)
if err != nil {
return err
}
nss, err := namespaces.List(ctx)
if err != nil {
return err
}
if quiet {
for _, ns := range nss {
fmt.Println(ns)
}
} else {
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
fmt.Fprintln(tw, "NAME\tLABELS\t")
for _, ns := range nss {
labels, err := namespaces.Labels(ctx, ns)
if err != nil {
return err
}
var labelStrings []string
for k, v := range labels {
labelStrings = append(labelStrings, strings.Join([]string{k, v}, "="))
}
sort.Strings(labelStrings)
fmt.Fprintf(tw, "%v\t%v\t\n", ns, strings.Join(labelStrings, ","))
}
tw.Flush()
}
return nil
},
}
var namespacesRemoveCommand = cli.Command{
Name: "remove",
Aliases: []string{"rm"},
Usage: "Remove one or more namespaces",
ArgsUsage: "[flags] <name> [<name>, ...]",
Description: "Remove one or more namespaces. For now, the namespace must be empty.",
Action: func(clicontext *cli.Context) error {
var (
ctx = context.Background()
exitErr error
)
namespaces, err := getNamespacesService(clicontext)
if err != nil {
return err
}
for _, target := range clicontext.Args() {
if err := namespaces.Delete(ctx, target); err != nil {
if !metadata.IsNotFound(err) {
if exitErr == nil {
exitErr = errors.Wrapf(err, "unable to delete %v", target)
}
log.G(ctx).WithError(err).Errorf("unable to delete %v", target)
continue
}
}
fmt.Println(target)
}
return exitErr
},
}

View File

@ -1,7 +1,6 @@
package main
import (
gocontext "context"
"errors"
"github.com/containerd/containerd/api/services/execution"
@ -13,6 +12,9 @@ var pauseCommand = cli.Command{
Usage: "pause an existing container",
ArgsUsage: "CONTAINER",
Action: func(context *cli.Context) error {
ctx, cancel := appContext(context)
defer cancel()
tasks, err := getTasksService(context)
if err != nil {
return err
@ -21,7 +23,7 @@ var pauseCommand = cli.Command{
if id == "" {
return errors.New("container id must be provided")
}
_, err = tasks.Pause(gocontext.Background(), &execution.PauseRequest{
_, err = tasks.Pause(ctx, &execution.PauseRequest{
ContainerID: id,
})
return err

View File

@ -1,7 +1,6 @@
package main
import (
gocontext "context"
"fmt"
"os"
"text/tabwriter"
@ -21,7 +20,12 @@ var psCommand = cli.Command{
},
},
Action: func(context *cli.Context) error {
id := context.String("id")
var (
id = context.String("id")
ctx, cancel = appContext(context)
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}
@ -35,7 +39,7 @@ var psCommand = cli.Command{
return err
}
resp, err := tasks.Processes(gocontext.Background(), pr)
resp, err := tasks.Processes(ctx, pr)
if err != nil {
return err
}

View File

@ -1,7 +1,6 @@
package main
import (
gocontext "context"
"errors"
"github.com/containerd/containerd/api/services/execution"
@ -13,6 +12,9 @@ var resumeCommand = cli.Command{
Usage: "resume a paused container",
ArgsUsage: "CONTAINER",
Action: func(context *cli.Context) error {
ctx, cancel := appContext(context)
defer cancel()
tasks, err := getTasksService(context)
if err != nil {
return err
@ -21,7 +23,7 @@ var resumeCommand = cli.Command{
if id == "" {
return errors.New("container id must be provided")
}
_, err = tasks.Resume(gocontext.Background(), &execution.ResumeRequest{
_, err = tasks.Resume(ctx, &execution.ResumeRequest{
ContainerID: id,
})
return err

View File

@ -1,7 +1,6 @@
package main
import (
gocontext "context"
"encoding/json"
"fmt"
"io/ioutil"
@ -79,9 +78,11 @@ var runCommand = cli.Command{
mounts []mount.Mount
imageConfig ocispec.Image
ctx = gocontext.Background()
id = context.String("id")
ctx, cancel = appContext(context)
id = context.String("id")
)
defer cancel()
if id == "" {
return errors.New("container id must be provided")
}

View File

@ -1,7 +1,6 @@
package main
import (
"context"
"errors"
"fmt"
@ -20,6 +19,9 @@ var snapshotCommand = cli.Command{
},
},
Action: func(clicontext *cli.Context) error {
ctx, cancel := appContext(clicontext)
defer cancel()
id := clicontext.String("id")
if id == "" {
return errors.New("container id must be provided")
@ -37,7 +39,7 @@ var snapshotCommand = cli.Command{
contentRef := fmt.Sprintf("diff-%s", id)
d, err := rootfs.Diff(context.TODO(), id, contentRef, snapshotter, differ)
d, err := rootfs.Diff(ctx, id, contentRef, snapshotter, differ)
if err != nil {
return err
}

View File

@ -18,14 +18,17 @@ import (
diffapi "github.com/containerd/containerd/api/services/diff"
"github.com/containerd/containerd/api/services/execution"
imagesapi "github.com/containerd/containerd/api/services/images"
namespacesapi "github.com/containerd/containerd/api/services/namespaces"
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
versionservice "github.com/containerd/containerd/api/services/version"
"github.com/containerd/containerd/api/types/task"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/namespaces"
contentservice "github.com/containerd/containerd/services/content"
"github.com/containerd/containerd/services/diff"
imagesservice "github.com/containerd/containerd/services/images"
namespacesservice "github.com/containerd/containerd/services/namespaces"
snapshotservice "github.com/containerd/containerd/services/snapshot"
"github.com/containerd/containerd/snapshot"
specs "github.com/opencontainers/runtime-spec/specs-go"
@ -35,6 +38,38 @@ import (
var grpcConn *grpc.ClientConn
// appContext returns the context for a command. Should only be called once per
// command, near the start.
//
// This will ensure the namespace is picked up and set the timeout, if one is
// defined.
func appContext(clicontext *cli.Context) (gocontext.Context, gocontext.CancelFunc) {
var (
ctx = gocontext.Background()
timeout = clicontext.GlobalDuration("timeout")
namespace = clicontext.GlobalString("namespace")
cancel = func() {}
)
ctx = namespaces.WithNamespace(ctx, namespace)
if timeout > 0 {
ctx, cancel = gocontext.WithTimeout(ctx, timeout)
} else {
ctx, cancel = gocontext.WithCancel(ctx)
}
return ctx, cancel
}
func getNamespacesService(clicontext *cli.Context) (namespaces.Store, error) {
conn, err := getGRPCConnection(clicontext)
if err != nil {
return nil, err
}
return namespacesservice.NewStoreFromClient(namespacesapi.NewNamespacesClient(conn)), nil
}
func getContainersService(context *cli.Context) (containersapi.ContainersClient, error) {
conn, err := getGRPCConnection(context)
if err != nil {

2
cmd/dist/active.go vendored
View File

@ -32,7 +32,7 @@ var activeCommand = cli.Command{
match = context.Args().First()
)
ctx, cancel := appContext()
ctx, cancel := appContext(context)
defer cancel()
cs, err := resolveContentStore(context)

2
cmd/dist/apply.go vendored
View File

@ -18,7 +18,7 @@ var applyCommand = cli.Command{
var (
dir = context.Args().First()
)
ctx, cancel := appContext()
ctx, cancel := appContext(context)
defer cancel()
log.G(ctx).Info("applying layer from stdin")

26
cmd/dist/common.go vendored
View File

@ -3,6 +3,7 @@ package main
import (
"bufio"
"context"
contextpkg "context"
"crypto/tls"
"encoding/json"
"fmt"
@ -17,6 +18,7 @@ import (
imagesapi "github.com/containerd/containerd/api/services/images"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker"
"github.com/containerd/containerd/rootfs"
@ -54,6 +56,30 @@ func getClient(context *cli.Context) (*containerd.Client, error) {
return containerd.New(address)
}
// appContext returns the context for a command. Should only be called once per
// command, near the start.
//
// This will ensure the namespace is picked up and set the timeout, if one is
// defined.
func appContext(clicontext *cli.Context) (contextpkg.Context, contextpkg.CancelFunc) {
var (
ctx = contextpkg.Background()
timeout = clicontext.GlobalDuration("timeout")
namespace = clicontext.GlobalString("namespace")
cancel = func() {}
)
ctx = namespaces.WithNamespace(ctx, namespace)
if timeout > 0 {
ctx, cancel = contextpkg.WithTimeout(ctx, timeout)
} else {
ctx, cancel = contextpkg.WithCancel(ctx)
}
return ctx, cancel
}
func resolveContentStore(context *cli.Context) (content.Store, error) {
conn, err := connectGRPC(context)
if err != nil {

2
cmd/dist/delete.go vendored
View File

@ -25,7 +25,7 @@ var deleteCommand = cli.Command{
args = []string(context.Args())
exitError error
)
ctx, cancel := appContext()
ctx, cancel := appContext(context)
defer cancel()
conn, err := connectGRPC(context)

2
cmd/dist/edit.go vendored
View File

@ -30,7 +30,7 @@ var editCommand = cli.Command{
validate = context.String("validate")
object = context.Args().First()
)
ctx, cancel := appContext()
ctx, cancel := appContext(context)
defer cancel()
if validate != "" {

2
cmd/dist/fetch.go vendored
View File

@ -44,7 +44,7 @@ Most of this is experimental and there are few leaps to make this work.`,
var (
ref = clicontext.Args().First()
)
ctx, cancel := appContext()
ctx, cancel := appContext(clicontext)
defer cancel()
conn, err := connectGRPC(clicontext)

View File

@ -22,7 +22,7 @@ var fetchObjectCommand = cli.Command{
var (
ref = context.Args().First()
)
ctx, cancel := appContext()
ctx, cancel := appContext(context)
defer cancel()
resolver, err := getResolver(ctx, context)

2
cmd/dist/get.go vendored
View File

@ -15,7 +15,7 @@ var getCommand = cli.Command{
Description: "Display the image object.",
Flags: []cli.Flag{},
Action: func(context *cli.Context) error {
ctx, cancel := appContext()
ctx, cancel := appContext(context)
defer cancel()
dgst, err := digest.Parse(context.Args().First())

4
cmd/dist/images.go vendored
View File

@ -30,7 +30,7 @@ var imagesListCommand = cli.Command{
Description: `List images registered with containerd.`,
Flags: []cli.Flag{},
Action: func(clicontext *cli.Context) error {
ctx, cancel := appContext()
ctx, cancel := appContext(clicontext)
defer cancel()
imageStore, err := resolveImageStore(clicontext)
@ -75,7 +75,7 @@ var imageRemoveCommand = cli.Command{
var (
exitErr error
)
ctx, cancel := appContext()
ctx, cancel := appContext(clicontext)
defer cancel()
imageStore, err := resolveImageStore(clicontext)

2
cmd/dist/ingest.go vendored
View File

@ -31,7 +31,7 @@ var ingestCommand = cli.Command{
expectedDigest = digest.Digest(context.String("expected-digest"))
)
ctx, cancel := appContext()
ctx, cancel := appContext(context)
defer cancel()
if err := expectedDigest.Validate(); expectedDigest != "" && err != nil {

2
cmd/dist/list.go vendored
View File

@ -29,7 +29,7 @@ var listCommand = cli.Command{
quiet = context.Bool("quiet")
args = []string(context.Args())
)
ctx, cancel := appContext()
ctx, cancel := appContext(context)
defer cancel()
cs, err := resolveContentStore(context)

16
cmd/dist/main.go vendored
View File

@ -1,7 +1,6 @@
package main
import (
contextpkg "context"
"fmt"
"os"
"time"
@ -22,14 +21,6 @@ func init() {
}
func appContext() (contextpkg.Context, contextpkg.CancelFunc) {
background := contextpkg.Background()
if timeout > 0 {
return contextpkg.WithTimeout(background, timeout)
}
return contextpkg.WithCancel(background)
}
func main() {
app := cli.NewApp()
app.Name = "dist"
@ -68,6 +59,12 @@ distribution tool
Usage: "address for containerd's GRPC server",
Value: "/run/containerd/containerd.sock",
},
cli.StringFlag{
Name: "namespace, n",
Usage: "namespace to use with commands",
Value: "default",
EnvVar: "CONTAINERD_NAMESPACE",
},
}
app.Commands = []cli.Command{
imageCommand,
@ -81,7 +78,6 @@ distribution tool
pushObjectCommand,
}
app.Before = func(context *cli.Context) error {
timeout = context.GlobalDuration("timeout")
if context.GlobalBool("debug") {
logrus.SetLevel(logrus.DebugLevel)
}

13
cmd/dist/pull.go vendored
View File

@ -39,7 +39,7 @@ command. As part of this process, we do the following:
ref = clicontext.Args().First()
)
ctx, cancel := appContext()
ctx, cancel := appContext(clicontext)
defer cancel()
cs, err := resolveContentStore(clicontext)
@ -100,20 +100,17 @@ command. As part of this process, we do the following:
}()
defer func() {
// we need new ctx here
ctx, cancel := appContext()
// we need new ctx here, since we run on return.
ctx, cancel := appContext(clicontext)
defer cancel()
// TODO(stevvooe): This section unpacks the layers and resolves the
// root filesystem chainid for the image. For now, we just print
// it, but we should keep track of this in the metadata storage.
image, err := imageStore.Get(ctx, resolvedImageName)
if err != nil {
log.G(ctx).WithError(err).Fatal("Failed to get image")
log.G(ctx).WithError(err).Fatal("failed to get image")
}
layers, err := getImageLayers(ctx, image, cs)
if err != nil {
log.G(ctx).WithError(err).Fatal("Failed to get rootfs layers")
log.G(ctx).WithError(err).Fatal("failed to get rootfs layers")
}
conn, err := connectGRPC(clicontext)

2
cmd/dist/push.go vendored
View File

@ -48,7 +48,7 @@ var pushCommand = cli.Command{
desc ocispec.Descriptor
)
ctx, cancel := appContext()
ctx, cancel := appContext(clicontext)
defer cancel()
client, err := getClient(clicontext)

View File

@ -26,7 +26,7 @@ var pushObjectCommand = cli.Command{
return err
}
ctx, cancel := appContext()
ctx, cancel := appContext(clicontext)
defer cancel()
resolver, err := getResolver(ctx, clicontext)

4
cmd/dist/rootfs.go vendored
View File

@ -32,7 +32,7 @@ var rootfsUnpackCommand = cli.Command{
ArgsUsage: "[flags] <digest>",
Flags: []cli.Flag{},
Action: func(clicontext *cli.Context) error {
ctx, cancel := appContext()
ctx, cancel := appContext(clicontext)
defer cancel()
dgst, err := digest.Parse(clicontext.Args().First())
@ -84,7 +84,7 @@ var rootfsPrepareCommand = cli.Command{
ArgsUsage: "[flags] <digest> <target>",
Flags: []cli.Flag{},
Action: func(clicontext *cli.Context) error {
ctx, cancel := appContext()
ctx, cancel := appContext(clicontext)
defer cancel()
if clicontext.NArg() != 2 {

View File

@ -2,7 +2,6 @@ package containerd
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
@ -27,7 +26,10 @@ func TestContainerList(t *testing.T) {
}
defer client.Close()
containers, err := client.Containers(context.Background())
ctx, cancel := testContext()
defer cancel()
containers, err := client.Containers(ctx)
if err != nil {
t.Errorf("container list returned error %v", err)
return
@ -53,12 +55,16 @@ func TestNewContainer(t *testing.T) {
t.Error(err)
return
}
container, err := client.NewContainer(context.Background(), id, WithSpec(spec))
ctx, cancel := testContext()
defer cancel()
container, err := client.NewContainer(ctx, id, WithSpec(spec))
if err != nil {
t.Error(err)
return
}
defer container.Delete(context.Background())
defer container.Delete(ctx)
if container.ID() != id {
t.Errorf("expected container id %q but received %q", id, container.ID())
}
@ -66,7 +72,7 @@ func TestNewContainer(t *testing.T) {
t.Error(err)
return
}
if err := container.Delete(context.Background()); err != nil {
if err := container.Delete(ctx); err != nil {
t.Error(err)
return
}
@ -83,9 +89,11 @@ func TestContainerStart(t *testing.T) {
defer client.Close()
var (
ctx = context.Background()
id = "ContainerStart"
ctx, cancel = testContext()
id = "ContainerStart"
)
defer cancel()
image, err := client.GetImage(ctx, testImage)
if err != nil {
t.Error(err)
@ -151,10 +159,12 @@ func TestContainerOutput(t *testing.T) {
defer client.Close()
var (
ctx = context.Background()
id = "ContainerOutput"
expected = "kingkoye"
ctx, cancel = testContext()
id = "ContainerOutput"
expected = "kingkoye"
)
defer cancel()
image, err := client.GetImage(ctx, testImage)
if err != nil {
t.Error(err)
@ -222,9 +232,11 @@ func TestContainerExec(t *testing.T) {
defer client.Close()
var (
ctx = context.Background()
id = "ContainerExec"
ctx, cancel = testContext()
id = "ContainerExec"
)
defer cancel()
image, err := client.GetImage(ctx, testImage)
if err != nil {
t.Error(err)
@ -307,9 +319,11 @@ func TestContainerProcesses(t *testing.T) {
defer client.Close()
var (
ctx = context.Background()
id = "ContainerProcesses"
ctx, cancel = testContext()
id = "ContainerProcesses"
)
defer cancel()
image, err := client.GetImage(ctx, testImage)
if err != nil {
t.Error(err)
@ -378,9 +392,11 @@ func TestContainerCloseStdin(t *testing.T) {
defer client.Close()
var (
ctx = context.Background()
id = "ContainerCloseStdin"
ctx, cancel = testContext()
id = "ContainerCloseStdin"
)
defer cancel()
image, err := client.GetImage(ctx, testImage)
if err != nil {
t.Error(err)
@ -460,9 +476,11 @@ func TestContainerAttach(t *testing.T) {
defer client.Close()
var (
ctx = context.Background()
id = "ContainerAttach"
ctx, cancel = testContext()
id = "ContainerAttach"
)
defer cancel()
image, err := client.GetImage(ctx, testImage)
if err != nil {
t.Error(err)

View File

@ -2,13 +2,36 @@ package metadata
import (
"github.com/boltdb/bolt"
"github.com/containerd/containerd/log"
)
// The layout where a "/" delineates a bucket is desribed in the following
// section. Please try to follow this as closely as possible when adding
// functionality. We can bolster this with helpers and more structure if that
// becomes an issue.
//
// Generically, we try to do the following:
//
// <version>/<namespace>/<object>/<key> -> <field>
//
// version: Currently, this is "v1". Additions can be made to v1 in a backwards
// compatible way. If the layout changes, a new version must be made, along
// with a migration.
//
// namespace: the namespace to which this object belongs.
//
// object: defines which object set is stored in the bucket. There are two
// special objects, "labels" and "indexes". The "labels" bucket stores the
// labels for the parent namespace. The "indexes" object is reserved for
// indexing objects, if we require in the future.
//
// key: object-specific key identifying the storage bucket for the objects
// contents.
var (
bucketKeyStorageVersion = []byte("v1")
bucketKeyImages = []byte("images")
bucketKeyContainers = []byte("containers")
bucketKeyVersion = []byte("v1")
bucketKeyObjectLabels = []byte("labels") // stores the labels for a namespace.
bucketKeyObjectIndexes = []byte("indexes") // reserved
bucketKeyObjectImages = []byte("images") // stores image objects
bucketKeyObjectContainers = []byte("containers") // stores container objects
bucketKeyDigest = []byte("digest")
bucketKeyMediaType = []byte("mediatype")
@ -22,21 +45,6 @@ var (
bucketKeyUpdatedAt = []byte("updatedat")
)
// InitDB will initialize the database for use. The database must be opened for
// write and the caller must not be holding an open transaction.
func InitDB(db *bolt.DB) error {
log.L.Debug("init db")
return db.Update(func(tx *bolt.Tx) error {
if _, err := createBucketIfNotExists(tx, bucketKeyStorageVersion, bucketKeyImages); err != nil {
return err
}
if _, err := createBucketIfNotExists(tx, bucketKeyStorageVersion, bucketKeyContainers); err != nil {
return err
}
return nil
})
}
func getBucket(tx *bolt.Tx, keys ...[]byte) *bolt.Bucket {
bkt := tx.Bucket(keys[0])
@ -66,44 +74,52 @@ func createBucketIfNotExists(tx *bolt.Tx, keys ...[]byte) (*bolt.Bucket, error)
return bkt, nil
}
func withImagesBucket(tx *bolt.Tx, fn func(bkt *bolt.Bucket) error) error {
bkt := getImagesBucket(tx)
if bkt == nil {
return ErrNotFound
func namespaceLabelsBucketPath(namespace string) [][]byte {
return [][]byte{bucketKeyVersion, []byte(namespace), bucketKeyObjectLabels}
}
func withNamespacesLabelsBucket(tx *bolt.Tx, namespace string, fn func(bkt *bolt.Bucket) error) error {
bkt, err := createBucketIfNotExists(tx, namespaceLabelsBucketPath(namespace)...)
if err != nil {
return err
}
return fn(bkt)
}
func withImageBucket(tx *bolt.Tx, name string, fn func(bkt *bolt.Bucket) error) error {
bkt := getImageBucket(tx, name)
if bkt == nil {
return ErrNotFound
func getNamespaceLabelsBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
return getBucket(tx, namespaceLabelsBucketPath(namespace)...)
}
func imagesBucketPath(namespace string) [][]byte {
return [][]byte{bucketKeyVersion, []byte(namespace), bucketKeyObjectImages}
}
func withImagesBucket(tx *bolt.Tx, namespace string, fn func(bkt *bolt.Bucket) error) error {
bkt, err := createBucketIfNotExists(tx, imagesBucketPath(namespace)...)
if err != nil {
return err
}
return fn(bkt)
}
func getImagesBucket(tx *bolt.Tx) *bolt.Bucket {
return getBucket(tx, bucketKeyStorageVersion, bucketKeyImages)
}
func getImageBucket(tx *bolt.Tx, name string) *bolt.Bucket {
return getBucket(tx, bucketKeyStorageVersion, bucketKeyImages, []byte(name))
func getImagesBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
return getBucket(tx, imagesBucketPath(namespace)...)
}
func createContainersBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
bkt, err := tx.CreateBucketIfNotExists(bucketKeyStorageVersion)
bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, bucketKeyObjectContainers)
if err != nil {
return nil, err
}
return bkt.CreateBucketIfNotExists(bucketKeyContainers)
return bkt, nil
}
func getContainersBucket(tx *bolt.Tx) *bolt.Bucket {
return getBucket(tx, bucketKeyStorageVersion, bucketKeyContainers)
return getBucket(tx, bucketKeyVersion, bucketKeyObjectContainers)
}
func getContainerBucket(tx *bolt.Tx, id string) *bolt.Bucket {
return getBucket(tx, bucketKeyStorageVersion, bucketKeyContainers, []byte(id))
return getBucket(tx, bucketKeyVersion, bucketKeyObjectContainers, []byte(id))
}

View File

@ -35,13 +35,13 @@ func (s *containerStore) Get(ctx context.Context, id string) (containers.Contain
func (s *containerStore) List(ctx context.Context, filter string) ([]containers.Container, error) {
var (
m = []containers.Container{}
m []containers.Container
bkt = getContainersBucket(s.tx)
)
if bkt == nil {
return m, nil
}
err := bkt.ForEach(func(k, v []byte) error {
if err := bkt.ForEach(func(k, v []byte) error {
cbkt := bkt.Bucket(k)
if cbkt == nil {
return nil
@ -53,8 +53,7 @@ func (s *containerStore) List(ctx context.Context, filter string) ([]containers.
}
m = append(m, container)
return nil
})
if err != nil {
}); err != nil {
return nil, err
}

View File

@ -5,6 +5,7 @@ import "github.com/pkg/errors"
var (
ErrExists = errors.New("metadata: exists")
ErrNotFound = errors.New("metadata: not found")
ErrNotEmpty = errors.New("metadata: namespace not empty")
)
// IsNotFound returns true if the error is due to a missing image.
@ -15,3 +16,7 @@ func IsNotFound(err error) bool {
func IsExists(err error) bool {
return errors.Cause(err) == ErrExists
}
func IsNotEmpty(err error) bool {
return errors.Cause(err) == ErrNotEmpty
}

View File

@ -7,6 +7,7 @@ import (
"github.com/boltdb/bolt"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/namespaces"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@ -21,10 +22,24 @@ func NewImageStore(tx *bolt.Tx) images.Store {
func (s *imageStore) Get(ctx context.Context, name string) (images.Image, error) {
var image images.Image
if err := withImageBucket(s.tx, name, func(bkt *bolt.Bucket) error {
image.Name = name
return readImage(&image, bkt)
}); err != nil {
namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return images.Image{}, err
}
bkt := getImagesBucket(s.tx, namespace)
if bkt == nil {
return images.Image{}, ErrNotFound
}
ibkt := bkt.Bucket([]byte(name))
if ibkt == nil {
return images.Image{}, ErrNotFound
}
image.Name = name
if err := readImage(&image, ibkt); err != nil {
return images.Image{}, err
}
@ -32,7 +47,12 @@ func (s *imageStore) Get(ctx context.Context, name string) (images.Image, error)
}
func (s *imageStore) Put(ctx context.Context, name string, desc ocispec.Descriptor) error {
return withImagesBucket(s.tx, func(bkt *bolt.Bucket) error {
namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
return withImagesBucket(s.tx, namespace, func(bkt *bolt.Bucket) error {
ibkt, err := bkt.CreateBucketIfNotExists([]byte(name))
if err != nil {
return err
@ -64,23 +84,30 @@ func (s *imageStore) Put(ctx context.Context, name string, desc ocispec.Descript
func (s *imageStore) List(ctx context.Context) ([]images.Image, error) {
var m []images.Image
namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return nil, err
}
if err := withImagesBucket(s.tx, func(bkt *bolt.Bucket) error {
return bkt.ForEach(func(k, v []byte) error {
var (
image = images.Image{
Name: string(k),
}
kbkt = bkt.Bucket(k)
)
bkt := getImagesBucket(s.tx, namespace)
if bkt == nil {
return nil, nil // empty store
}
if err := readImage(&image, kbkt); err != nil {
return err
if err := bkt.ForEach(func(k, v []byte) error {
var (
image = images.Image{
Name: string(k),
}
kbkt = bkt.Bucket(k)
)
m = append(m, image)
return nil
})
if err := readImage(&image, kbkt); err != nil {
return err
}
m = append(m, image)
return nil
}); err != nil {
return nil, err
}
@ -89,7 +116,12 @@ func (s *imageStore) List(ctx context.Context) ([]images.Image, error) {
}
func (s *imageStore) Delete(ctx context.Context, name string) error {
return withImagesBucket(s.tx, func(bkt *bolt.Bucket) error {
namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
return withImagesBucket(s.tx, namespace, func(bkt *bolt.Bucket) error {
err := bkt.DeleteBucket([]byte(name))
if err == bolt.ErrBucketNotFound {
return ErrNotFound

145
metadata/namespaces.go Normal file
View File

@ -0,0 +1,145 @@
package metadata
import (
"context"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/namespaces"
)
type namespaceStore struct {
tx *bolt.Tx
}
func NewNamespaceStore(tx *bolt.Tx) namespaces.Store {
return &namespaceStore{tx: tx}
}
func (s *namespaceStore) Create(ctx context.Context, namespace string, labels map[string]string) error {
topbkt, err := createBucketIfNotExists(s.tx, bucketKeyVersion)
if err != nil {
return err
}
// provides the already exists error.
bkt, err := topbkt.CreateBucket([]byte(namespace))
if err != nil {
if err == bolt.ErrBucketExists {
return ErrExists
}
return err
}
lbkt, err := bkt.CreateBucketIfNotExists(bucketKeyObjectLabels)
if err != nil {
return err
}
for k, v := range labels {
if err := lbkt.Put([]byte(k), []byte(v)); err != nil {
return err
}
}
return nil
}
func (s *namespaceStore) Labels(ctx context.Context, namespace string) (map[string]string, error) {
labels := map[string]string{}
bkt := getNamespaceLabelsBucket(s.tx, namespace)
if bkt == nil {
return labels, nil
}
if err := bkt.ForEach(func(k, v []byte) error {
labels[string(k)] = string(v)
return nil
}); err != nil {
return nil, err
}
return labels, nil
}
func (s *namespaceStore) SetLabel(ctx context.Context, namespace, key, value string) error {
return withNamespacesLabelsBucket(s.tx, namespace, func(bkt *bolt.Bucket) error {
if value == "" {
return bkt.Delete([]byte(key))
}
return bkt.Put([]byte(key), []byte(value))
})
}
func (s *namespaceStore) List(ctx context.Context) ([]string, error) {
bkt := getBucket(s.tx, bucketKeyVersion)
if bkt == nil {
return nil, nil // no namespaces!
}
var namespaces []string
if err := bkt.ForEach(func(k, v []byte) error {
if v != nil {
return nil // not a bucket
}
namespaces = append(namespaces, string(k))
return nil
}); err != nil {
return nil, err
}
return namespaces, nil
}
func (s *namespaceStore) Delete(ctx context.Context, namespace string) error {
bkt := getBucket(s.tx, bucketKeyVersion)
if empty, err := s.namespaceEmpty(ctx, namespace); err != nil {
return err
} else if !empty {
return ErrNotEmpty
}
if err := bkt.DeleteBucket([]byte(namespace)); err != nil {
if err == bolt.ErrBucketNotFound {
return ErrNotFound
}
return err
}
return nil
}
func (s *namespaceStore) namespaceEmpty(ctx context.Context, namespace string) (bool, error) {
ctx = namespaces.WithNamespace(ctx, namespace)
// need to check the various object stores.
imageStore := NewImageStore(s.tx)
images, err := imageStore.List(ctx)
if err != nil {
return false, err
}
if len(images) > 0 {
return false, nil
}
containerStore := NewContainerStore(s.tx)
containers, err := containerStore.List(ctx, "")
if err != nil {
return false, err
}
if len(containers) > 0 {
return false, nil
}
// TODO(stevvooe): Need to add check for content store, as well. Still need
// to make content store namespace aware.
return true, nil
}

42
namespaces/context.go Normal file
View File

@ -0,0 +1,42 @@
package namespaces
import (
"github.com/pkg/errors"
"golang.org/x/net/context"
)
var (
errNamespaceRequired = errors.New("namespace is required")
)
type namespaceKey struct{}
func WithNamespace(ctx context.Context, namespace string) context.Context {
ctx = context.WithValue(ctx, namespaceKey{}, namespace) // set our key for namespace
// also store on the grpc headers so it gets picked up by any clients that
// are using this.
return withGRPCNamespaceHeader(ctx, namespace)
}
func Namespace(ctx context.Context) (string, bool) {
namespace, ok := ctx.Value(namespaceKey{}).(string)
if !ok {
return fromGRPCHeader(ctx)
}
return namespace, ok
}
func IsNamespaceRequired(err error) bool {
return errors.Cause(err) == errNamespaceRequired
}
func NamespaceRequired(ctx context.Context) (string, error) {
namespace, ok := Namespace(ctx)
if !ok || namespace == "" {
return "", errNamespaceRequired
}
return namespace, nil
}

View File

@ -0,0 +1,30 @@
package namespaces
import (
"context"
"testing"
)
func TestContext(t *testing.T) {
ctx := context.Background()
namespace, ok := Namespace(ctx)
if ok {
t.Fatal("namespace should not be present")
}
if namespace != "" {
t.Fatalf("namespace should not be defined: got %q", namespace)
}
expected := "test"
nctx := WithNamespace(ctx, expected)
namespace, ok = Namespace(nctx)
if !ok {
t.Fatal("expected to find a namespace")
}
if namespace != expected {
t.Fatalf("unexpected namespace: %q != %q", namespace, expected)
}
}

44
namespaces/grpc.go Normal file
View File

@ -0,0 +1,44 @@
package namespaces
import (
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
)
const (
// GRPCHeader defines the header name for specifying a containerd namespace.
GRPCHeader = "containerd-namespace"
)
// NOTE(stevvooe): We can stub this file out if we don't want a grpc dependency here.
func withGRPCNamespaceHeader(ctx context.Context, namespace string) context.Context {
// also store on the grpc headers so it gets picked up by any clients that
// are using this.
nsheader := metadata.Pairs(GRPCHeader, namespace)
md, ok := metadata.FromOutgoingContext(ctx) // merge with outgoing context.
if !ok {
md = nsheader
} else {
// order ensures the latest is first in this list.
md = metadata.Join(nsheader, md)
}
return metadata.NewOutgoingContext(ctx, md)
}
func fromGRPCHeader(ctx context.Context) (string, bool) {
// try to extract for use in grpc servers.
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
// TODO(stevvooe): Check outgoing context?
return "", false
}
values := md[GRPCHeader]
if len(values) == 0 {
return "", false
}
return values[0], true
}

21
namespaces/store.go Normal file
View File

@ -0,0 +1,21 @@
package namespaces
import "context"
// Store provides introspection about namespaces.
//
// Note that these are slightly different than other objects, which are record
// oriented. A namespace is really just a name and a set of labels. Objects
// that belong to a namespace are returned when the namespace is assigned to a
// given context.
//
//
type Store interface {
Create(ctx context.Context, namespace string, labels map[string]string) error
Labels(ctx context.Context, namespace string) (map[string]string, error)
SetLabel(ctx context.Context, namespace, key, value string) error
List(ctx context.Context) ([]string, error)
// Delete removes the namespace. The namespace must be empty to be deleted.
Delete(ctx context.Context, namespace string) error
}

View File

@ -5,6 +5,7 @@ import (
"github.com/containerd/containerd/api/types/descriptor"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/metadata"
"github.com/containerd/containerd/namespaces"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"google.golang.org/grpc"
@ -82,6 +83,8 @@ func mapGRPCError(err error, id string) error {
return grpc.Errorf(codes.NotFound, "image %v not found", id)
case metadata.IsExists(err):
return grpc.Errorf(codes.AlreadyExists, "image %v already exists", id)
case namespaces.IsNamespaceRequired(err):
return grpc.Errorf(codes.InvalidArgument, "namespace required, please set %q header", namespaces.GRPCHeader)
}
return err

View File

@ -0,0 +1,95 @@
package namespaces
import (
"context"
"strings"
api "github.com/containerd/containerd/api/services/namespaces"
"github.com/containerd/containerd/namespaces"
"github.com/gogo/protobuf/types"
)
func NewStoreFromClient(client api.NamespacesClient) namespaces.Store {
return &remote{client: client}
}
type remote struct {
client api.NamespacesClient
}
func (r *remote) Create(ctx context.Context, namespace string, labels map[string]string) error {
var req api.CreateNamespaceRequest
req.Namespace = api.Namespace{
Name: namespace,
Labels: labels,
}
_, err := r.client.Create(ctx, &req)
if err != nil {
return rewriteGRPCError(err)
}
return nil
}
func (r *remote) Labels(ctx context.Context, namespace string) (map[string]string, error) {
var req api.GetNamespaceRequest
req.Name = namespace
resp, err := r.client.Get(ctx, &req)
if err != nil {
return nil, rewriteGRPCError(err)
}
return resp.Namespace.Labels, nil
}
func (r *remote) SetLabel(ctx context.Context, namespace, key, value string) error {
var req api.UpdateNamespaceRequest
req.Namespace = api.Namespace{
Name: namespace,
Labels: map[string]string{key: value},
}
req.UpdateMask = &types.FieldMask{
Paths: []string{strings.Join([]string{"labels", key}, ".")},
}
_, err := r.client.Update(ctx, &req)
if err != nil {
return rewriteGRPCError(err)
}
return nil
}
func (r *remote) List(ctx context.Context) ([]string, error) {
var req api.ListNamespacesRequest
resp, err := r.client.List(ctx, &req)
if err != nil {
return nil, rewriteGRPCError(err)
}
var namespaces []string
for _, ns := range resp.Namespaces {
namespaces = append(namespaces, ns.Name)
}
return namespaces, nil
}
func (r *remote) Delete(ctx context.Context, namespace string) error {
var req api.DeleteNamespaceRequest
req.Name = namespace
_, err := r.client.Delete(ctx, &req)
if err != nil {
return rewriteGRPCError(err)
}
return nil
}

View File

@ -0,0 +1,36 @@
package namespaces
import (
"github.com/containerd/containerd/metadata"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
func mapGRPCError(err error, id string) error {
switch {
case metadata.IsNotFound(err):
return grpc.Errorf(codes.NotFound, "namespace %v not found", id)
case metadata.IsExists(err):
return grpc.Errorf(codes.AlreadyExists, "namespace %v already exists", id)
case metadata.IsNotEmpty(err):
return grpc.Errorf(codes.FailedPrecondition, "namespace %v must be empty", id)
}
return err
}
func rewriteGRPCError(err error) error {
if err == nil {
return err
}
switch grpc.Code(errors.Cause(err)) {
case codes.AlreadyExists:
return metadata.ErrExists
case codes.NotFound:
return metadata.ErrNotFound
}
return err
}

View File

@ -0,0 +1,164 @@
package namespaces
import (
"strings"
"github.com/boltdb/bolt"
api "github.com/containerd/containerd/api/services/namespaces"
"github.com/containerd/containerd/metadata"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/plugin"
"github.com/golang/protobuf/ptypes/empty"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
func init() {
plugin.Register("namespaces-grpc", &plugin.Registration{
Type: plugin.GRPCPlugin,
Init: func(ic *plugin.InitContext) (interface{}, error) {
return NewService(ic.Meta), nil
},
})
}
type Service struct {
db *bolt.DB
}
var _ api.NamespacesServer = &Service{}
func NewService(db *bolt.DB) api.NamespacesServer {
return &Service{db: db}
}
func (s *Service) Register(server *grpc.Server) error {
api.RegisterNamespacesServer(server, s)
return nil
}
func (s *Service) Get(ctx context.Context, req *api.GetNamespaceRequest) (*api.GetNamespaceResponse, error) {
var resp api.GetNamespaceResponse
return &resp, s.withStoreView(ctx, func(ctx context.Context, store namespaces.Store) error {
labels, err := store.Labels(ctx, req.Name)
if err != nil {
return mapGRPCError(err, req.Name)
}
resp.Namespace = api.Namespace{
Name: req.Name,
Labels: labels,
}
return nil
})
}
func (s *Service) List(ctx context.Context, req *api.ListNamespacesRequest) (*api.ListNamespacesResponse, error) {
var resp api.ListNamespacesResponse
return &resp, s.withStoreView(ctx, func(ctx context.Context, store namespaces.Store) error {
namespaces, err := store.List(ctx)
if err != nil {
return err
}
for _, namespace := range namespaces {
labels, err := store.Labels(ctx, namespace)
if err != nil {
// In general, this should be unlikely, since we are holding a
// transaction to service this request.
return mapGRPCError(err, namespace)
}
resp.Namespaces = append(resp.Namespaces, api.Namespace{
Name: namespace,
Labels: labels,
})
}
return nil
})
}
func (s *Service) Create(ctx context.Context, req *api.CreateNamespaceRequest) (*api.CreateNamespaceResponse, error) {
var resp api.CreateNamespaceResponse
return &resp, s.withStoreUpdate(ctx, func(ctx context.Context, store namespaces.Store) error {
if err := store.Create(ctx, req.Namespace.Name, req.Namespace.Labels); err != nil {
return mapGRPCError(err, req.Namespace.Name)
}
for k, v := range req.Namespace.Labels {
if err := store.SetLabel(ctx, req.Namespace.Name, k, v); err != nil {
return err
}
}
resp.Namespace = req.Namespace
return nil
})
}
func (s *Service) Update(ctx context.Context, req *api.UpdateNamespaceRequest) (*api.UpdateNamespaceResponse, error) {
var resp api.UpdateNamespaceResponse
return &resp, s.withStoreUpdate(ctx, func(ctx context.Context, store namespaces.Store) error {
if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 {
for _, path := range req.UpdateMask.Paths {
switch {
case strings.HasPrefix(path, "labels."):
key := strings.TrimPrefix(path, "labels.")
if err := store.SetLabel(ctx, req.Namespace.Name, key, req.Namespace.Labels[key]); err != nil {
return err
}
default:
return grpc.Errorf(codes.InvalidArgument, "cannot update %q field", path)
}
}
} else {
// clear out the existing labels and then set them to the incoming request.
// get current set of labels
labels, err := store.Labels(ctx, req.Namespace.Name)
if err != nil {
return mapGRPCError(err, req.Namespace.Name)
}
for k := range labels {
if err := store.SetLabel(ctx, req.Namespace.Name, k, ""); err != nil {
return err
}
}
for k, v := range req.Namespace.Labels {
if err := store.SetLabel(ctx, req.Namespace.Name, k, v); err != nil {
return err
}
}
}
return nil
})
}
func (s *Service) Delete(ctx context.Context, req *api.DeleteNamespaceRequest) (*empty.Empty, error) {
return &empty.Empty{}, s.withStoreUpdate(ctx, func(ctx context.Context, store namespaces.Store) error {
return mapGRPCError(store.Delete(ctx, req.Name), req.Name)
})
}
func (s *Service) withStore(ctx context.Context, fn func(ctx context.Context, store namespaces.Store) error) func(tx *bolt.Tx) error {
return func(tx *bolt.Tx) error { return fn(ctx, metadata.NewNamespaceStore(tx)) }
}
func (s *Service) withStoreView(ctx context.Context, fn func(ctx context.Context, store namespaces.Store) error) error {
return s.db.View(s.withStore(ctx, fn))
}
func (s *Service) withStoreUpdate(ctx context.Context, fn func(ctx context.Context, store namespaces.Store) error) error {
return s.db.Update(s.withStore(ctx, fn))
}