diff --git a/api/services/namespaces/namespace.pb.go b/api/services/namespaces/namespace.pb.go index 650495c9bfd9..28a9e3db48e4 100644 --- a/api/services/namespaces/namespace.pb.go +++ b/api/services/namespaces/namespace.pb.go @@ -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, } diff --git a/api/services/namespaces/namespace.proto b/api/services/namespaces/namespace.proto index 4c0eab34036f..4927cff8527e 100644 --- a/api/services/namespaces/namespace.proto +++ b/api/services/namespaces/namespace.proto @@ -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; } diff --git a/checkpoint_test.go b/checkpoint_test.go index 3315ab15279c..df1491208245 100644 --- a/checkpoint_test.go +++ b/checkpoint_test.go @@ -1,7 +1,6 @@ package containerd import ( - "context" "syscall" "testing" ) @@ -17,9 +16,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) @@ -107,7 +108,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) diff --git a/client.go b/client.go index 7985e8f0881f..7d3c4ccff3fe 100644 --- a/client.go +++ b/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) } diff --git a/client_test.go b/client_test.go index 5bb0e5b0184b..7375563d6c6b 100644 --- a/client_test.go +++ b/client_test.go @@ -10,6 +10,8 @@ import ( "syscall" "testing" "time" + + "github.com/containerd/containerd/namespaces" ) const ( @@ -29,14 +31,23 @@ 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()) } 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", @@ -55,12 +66,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) @@ -92,13 +104,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 } @@ -127,13 +140,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 diff --git a/cmd/containerd/builtins.go b/cmd/containerd/builtins.go index 9386b1c1887c..a21d4a5dbfe5 100644 --- a/cmd/containerd/builtins.go +++ b/cmd/containerd/builtins.go @@ -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" ) diff --git a/cmd/containerd/main.go b/cmd/containerd/main.go index 2f82e3f6039d..a76f1070b083 100644 --- a/cmd/containerd/main.go +++ b/cmd/containerd/main.go @@ -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) } diff --git a/cmd/ctr/checkpoint.go b/cmd/ctr/checkpoint.go index fc35cb335806..29590069a77b 100644 --- a/cmd/ctr/checkpoint.go +++ b/cmd/ctr/checkpoint.go @@ -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") } diff --git a/cmd/ctr/delete.go b/cmd/ctr/delete.go index a373c059c772..920695dfa408 100644 --- a/cmd/ctr/delete.go +++ b/cmd/ctr/delete.go @@ -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, }) diff --git a/cmd/ctr/events.go b/cmd/ctr/events.go index 6bd31bd1719a..001db8675602 100644 --- a/cmd/ctr/events.go +++ b/cmd/ctr/events.go @@ -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 } diff --git a/cmd/ctr/exec.go b/cmd/ctr/exec.go index 57a8a7c824b6..2df95db4c1be 100644 --- a/cmd/ctr/exec.go +++ b/cmd/ctr/exec.go @@ -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") } diff --git a/cmd/ctr/info.go b/cmd/ctr/info.go index 2cfba1151ec7..3dec3bccb392 100644 --- a/cmd/ctr/info.go +++ b/cmd/ctr/info.go @@ -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 } diff --git a/cmd/ctr/kill.go b/cmd/ctr/kill.go index afa12144e4a8..b2b9fb2a106e 100644 --- a/cmd/ctr/kill.go +++ b/cmd/ctr/kill.go @@ -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 } diff --git a/cmd/ctr/list.go b/cmd/ctr/list.go index 16e52c5dfad5..b8a5ab756829 100644 --- a/cmd/ctr/list.go +++ b/cmd/ctr/list.go @@ -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 } diff --git a/cmd/ctr/main.go b/cmd/ctr/main.go index 84bc711b0ab7..acd527fd0606 100644 --- a/cmd/ctr/main.go +++ b/cmd/ctr/main.go @@ -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, diff --git a/cmd/ctr/namespaces.go b/cmd/ctr/namespaces.go new file mode 100644 index 000000000000..283bfe464d97 --- /dev/null +++ b/cmd/ctr/namespaces.go @@ -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] [= 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] [=, ...]", + 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] [, ...]", + 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 + + }, +} diff --git a/cmd/ctr/pause.go b/cmd/ctr/pause.go index 42214239656c..e68e5bdad972 100644 --- a/cmd/ctr/pause.go +++ b/cmd/ctr/pause.go @@ -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 diff --git a/cmd/ctr/ps.go b/cmd/ctr/ps.go index c540d1e96a77..b6ff9f88cda1 100644 --- a/cmd/ctr/ps.go +++ b/cmd/ctr/ps.go @@ -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 } diff --git a/cmd/ctr/resume.go b/cmd/ctr/resume.go index e22d46d06c50..f0cc6210a09f 100644 --- a/cmd/ctr/resume.go +++ b/cmd/ctr/resume.go @@ -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 diff --git a/cmd/ctr/run.go b/cmd/ctr/run.go index eae380606891..e655ee760b98 100644 --- a/cmd/ctr/run.go +++ b/cmd/ctr/run.go @@ -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") } diff --git a/cmd/ctr/snapshot.go b/cmd/ctr/snapshot.go index 631f7d914cc9..825741fc0dc3 100644 --- a/cmd/ctr/snapshot.go +++ b/cmd/ctr/snapshot.go @@ -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 } diff --git a/cmd/ctr/utils.go b/cmd/ctr/utils.go index 3dfabeeffe90..01c429433cce 100644 --- a/cmd/ctr/utils.go +++ b/cmd/ctr/utils.go @@ -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 { diff --git a/cmd/dist/active.go b/cmd/dist/active.go index d339f6e18162..699c63af1146 100644 --- a/cmd/dist/active.go +++ b/cmd/dist/active.go @@ -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) diff --git a/cmd/dist/apply.go b/cmd/dist/apply.go index fe31dfa7e6b4..bbd702bf5f7b 100644 --- a/cmd/dist/apply.go +++ b/cmd/dist/apply.go @@ -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") diff --git a/cmd/dist/common.go b/cmd/dist/common.go index efd7df07653d..501a37ab9783 100644 --- a/cmd/dist/common.go +++ b/cmd/dist/common.go @@ -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 { diff --git a/cmd/dist/delete.go b/cmd/dist/delete.go index 9e586835b55a..d8cb76062d97 100644 --- a/cmd/dist/delete.go +++ b/cmd/dist/delete.go @@ -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) diff --git a/cmd/dist/edit.go b/cmd/dist/edit.go index 0a0ad039f8b2..365aa2d41878 100644 --- a/cmd/dist/edit.go +++ b/cmd/dist/edit.go @@ -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 != "" { diff --git a/cmd/dist/fetch.go b/cmd/dist/fetch.go index c2de902c9f1e..507f760e10ee 100644 --- a/cmd/dist/fetch.go +++ b/cmd/dist/fetch.go @@ -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) diff --git a/cmd/dist/fetchobject.go b/cmd/dist/fetchobject.go index 2f8a43aabeee..cf4cf8303af3 100644 --- a/cmd/dist/fetchobject.go +++ b/cmd/dist/fetchobject.go @@ -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) diff --git a/cmd/dist/get.go b/cmd/dist/get.go index b36b0e3cb878..909805cd782a 100644 --- a/cmd/dist/get.go +++ b/cmd/dist/get.go @@ -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()) diff --git a/cmd/dist/images.go b/cmd/dist/images.go index 53fbb3dce9e1..9018eb508095 100644 --- a/cmd/dist/images.go +++ b/cmd/dist/images.go @@ -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) diff --git a/cmd/dist/ingest.go b/cmd/dist/ingest.go index 03d16e0bad4a..5adffe326ef4 100644 --- a/cmd/dist/ingest.go +++ b/cmd/dist/ingest.go @@ -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 { diff --git a/cmd/dist/list.go b/cmd/dist/list.go index 9a73b012391c..60c784943a0e 100644 --- a/cmd/dist/list.go +++ b/cmd/dist/list.go @@ -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) diff --git a/cmd/dist/main.go b/cmd/dist/main.go index db5b2e767805..d5da0ee19df1 100644 --- a/cmd/dist/main.go +++ b/cmd/dist/main.go @@ -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) } diff --git a/cmd/dist/pull.go b/cmd/dist/pull.go index 74bf1a2d0388..948b873f4e12 100644 --- a/cmd/dist/pull.go +++ b/cmd/dist/pull.go @@ -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) diff --git a/cmd/dist/push.go b/cmd/dist/push.go index 6ce7afbe6f30..439b33521bb1 100644 --- a/cmd/dist/push.go +++ b/cmd/dist/push.go @@ -48,7 +48,7 @@ var pushCommand = cli.Command{ desc ocispec.Descriptor ) - ctx, cancel := appContext() + ctx, cancel := appContext(clicontext) defer cancel() client, err := getClient(clicontext) diff --git a/cmd/dist/pushobject.go b/cmd/dist/pushobject.go index f69778da9436..196036a8883e 100644 --- a/cmd/dist/pushobject.go +++ b/cmd/dist/pushobject.go @@ -26,7 +26,7 @@ var pushObjectCommand = cli.Command{ return err } - ctx, cancel := appContext() + ctx, cancel := appContext(clicontext) defer cancel() resolver, err := getResolver(ctx, clicontext) diff --git a/cmd/dist/rootfs.go b/cmd/dist/rootfs.go index d8711959f199..ea496f25d591 100644 --- a/cmd/dist/rootfs.go +++ b/cmd/dist/rootfs.go @@ -32,7 +32,7 @@ var rootfsUnpackCommand = cli.Command{ ArgsUsage: "[flags] ", 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] ", Flags: []cli.Flag{}, Action: func(clicontext *cli.Context) error { - ctx, cancel := appContext() + ctx, cancel := appContext(clicontext) defer cancel() if clicontext.NArg() != 2 { diff --git a/container_test.go b/container_test.go index 7f12e2ec2883..9245c1203d17 100644 --- a/container_test.go +++ b/container_test.go @@ -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) diff --git a/metadata/buckets.go b/metadata/buckets.go index 2da561cd4238..7eca8a2b82e1 100644 --- a/metadata/buckets.go +++ b/metadata/buckets.go @@ -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: 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)...) +} - return fn(bkt) +func imagesBucketPath(namespace string) [][]byte { + return [][]byte{bucketKeyVersion, []byte(namespace), bucketKeyObjectImages} } -func getImagesBucket(tx *bolt.Tx) *bolt.Bucket { - return getBucket(tx, bucketKeyStorageVersion, bucketKeyImages) +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 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)) } diff --git a/metadata/containers.go b/metadata/containers.go index d1fe67f9d261..f1faaa0f342c 100644 --- a/metadata/containers.go +++ b/metadata/containers.go @@ -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 } diff --git a/metadata/errors.go b/metadata/errors.go index ab370a5ddce2..4607ec48e25d 100644 --- a/metadata/errors.go +++ b/metadata/errors.go @@ -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 +} diff --git a/metadata/images.go b/metadata/images.go index a922f7b666fe..43c703019881 100644 --- a/metadata/images.go +++ b/metadata/images.go @@ -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) + ) + + if err := readImage(&image, kbkt); err != nil { + return err + } - m = append(m, image) - return nil - }) + 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 diff --git a/metadata/namespaces.go b/metadata/namespaces.go new file mode 100644 index 000000000000..ffe898f67fec --- /dev/null +++ b/metadata/namespaces.go @@ -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 +} diff --git a/namespaces/context.go b/namespaces/context.go new file mode 100644 index 000000000000..fe5049e98372 --- /dev/null +++ b/namespaces/context.go @@ -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 +} diff --git a/namespaces/context_test.go b/namespaces/context_test.go new file mode 100644 index 000000000000..20e2b0927639 --- /dev/null +++ b/namespaces/context_test.go @@ -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) + } +} diff --git a/namespaces/grpc.go b/namespaces/grpc.go new file mode 100644 index 000000000000..c18fc933d133 --- /dev/null +++ b/namespaces/grpc.go @@ -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 +} diff --git a/namespaces/store.go b/namespaces/store.go new file mode 100644 index 000000000000..68ff714bb7fb --- /dev/null +++ b/namespaces/store.go @@ -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 +} diff --git a/services/images/helpers.go b/services/images/helpers.go index 52be324d9ac2..19ce9e18af39 100644 --- a/services/images/helpers.go +++ b/services/images/helpers.go @@ -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 diff --git a/services/namespaces/client.go b/services/namespaces/client.go new file mode 100644 index 000000000000..e92314dccde6 --- /dev/null +++ b/services/namespaces/client.go @@ -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 +} diff --git a/services/namespaces/helpers.go b/services/namespaces/helpers.go new file mode 100644 index 000000000000..8cb8f55ad8ce --- /dev/null +++ b/services/namespaces/helpers.go @@ -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 +} diff --git a/services/namespaces/service.go b/services/namespaces/service.go new file mode 100644 index 000000000000..4a5388e4d62b --- /dev/null +++ b/services/namespaces/service.go @@ -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)) +}