Merge pull request #963 from stevvooe/namespaces-support
namespaces: support within containerd
This commit is contained in:
commit
bdf9f5f738
@ -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,
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
15
client.go
15
client.go
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
201
cmd/ctr/namespaces.go
Normal 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
|
||||
|
||||
},
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
2
cmd/dist/active.go
vendored
@ -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
2
cmd/dist/apply.go
vendored
@ -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
26
cmd/dist/common.go
vendored
@ -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
2
cmd/dist/delete.go
vendored
@ -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
2
cmd/dist/edit.go
vendored
@ -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
2
cmd/dist/fetch.go
vendored
@ -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)
|
||||
|
2
cmd/dist/fetchobject.go
vendored
2
cmd/dist/fetchobject.go
vendored
@ -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
2
cmd/dist/get.go
vendored
@ -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
4
cmd/dist/images.go
vendored
@ -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
2
cmd/dist/ingest.go
vendored
@ -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
2
cmd/dist/list.go
vendored
@ -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
16
cmd/dist/main.go
vendored
@ -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
13
cmd/dist/pull.go
vendored
@ -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
2
cmd/dist/push.go
vendored
@ -48,7 +48,7 @@ var pushCommand = cli.Command{
|
||||
desc ocispec.Descriptor
|
||||
)
|
||||
|
||||
ctx, cancel := appContext()
|
||||
ctx, cancel := appContext(clicontext)
|
||||
defer cancel()
|
||||
|
||||
client, err := getClient(clicontext)
|
||||
|
2
cmd/dist/pushobject.go
vendored
2
cmd/dist/pushobject.go
vendored
@ -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
4
cmd/dist/rootfs.go
vendored
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
145
metadata/namespaces.go
Normal 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
42
namespaces/context.go
Normal 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
|
||||
}
|
30
namespaces/context_test.go
Normal file
30
namespaces/context_test.go
Normal 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
44
namespaces/grpc.go
Normal 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
21
namespaces/store.go
Normal 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
|
||||
}
|
@ -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
|
||||
|
95
services/namespaces/client.go
Normal file
95
services/namespaces/client.go
Normal 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
|
||||
}
|
36
services/namespaces/helpers.go
Normal file
36
services/namespaces/helpers.go
Normal 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
|
||||
}
|
164
services/namespaces/service.go
Normal file
164
services/namespaces/service.go
Normal 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))
|
||||
}
|
Loading…
Reference in New Issue
Block a user