diff --git a/api/services/containers/v1/containers.pb.go b/api/services/containers/v1/containers.pb.go index 366c7e895..8343631a3 100644 --- a/api/services/containers/v1/containers.pb.go +++ b/api/services/containers/v1/containers.pb.go @@ -125,7 +125,17 @@ func (*GetContainerResponse) ProtoMessage() {} func (*GetContainerResponse) Descriptor() ([]byte, []int) { return fileDescriptorContainers, []int{2} } type ListContainersRequest struct { - Filter string `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + // Filters contains one or more filters using the syntax defined in the + // containerd filter package. + // + // The returned result will be those that match any of the provided + // filters. Expanded, containers that match the following will be + // returned: + // + // filters[0] or filters[1] or ... or filters[n-1] or filters[n] + // + // If filters is zero-length or nil, all items will be returned. + Filters []string `protobuf:"bytes,1,rep,name=filters" json:"filters,omitempty"` } func (m *ListContainersRequest) Reset() { *m = ListContainersRequest{} } @@ -601,11 +611,20 @@ func (m *ListContainersRequest) MarshalTo(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Filter) > 0 { - dAtA[i] = 0xa - i++ - i = encodeVarintContainers(dAtA, i, uint64(len(m.Filter))) - i += copy(dAtA[i:], m.Filter) + if len(m.Filters) > 0 { + for _, s := range m.Filters { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } } return i, nil } @@ -878,9 +897,11 @@ func (m *GetContainerResponse) Size() (n int) { func (m *ListContainersRequest) Size() (n int) { var l int _ = l - l = len(m.Filter) - if l > 0 { - n += 1 + l + sovContainers(uint64(l)) + if len(m.Filters) > 0 { + for _, s := range m.Filters { + l = len(s) + n += 1 + l + sovContainers(uint64(l)) + } } return n } @@ -1019,7 +1040,7 @@ func (this *ListContainersRequest) String() string { return "nil" } s := strings.Join([]string{`&ListContainersRequest{`, - `Filter:` + fmt.Sprintf("%v", this.Filter) + `,`, + `Filters:` + fmt.Sprintf("%v", this.Filters) + `,`, `}`, }, "") return s @@ -1774,7 +1795,7 @@ func (m *ListContainersRequest) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Filter", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Filters", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1799,7 +1820,7 @@ func (m *ListContainersRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Filter = string(dAtA[iNdEx:postIndex]) + m.Filters = append(m.Filters, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex default: iNdEx = preIndex @@ -2445,52 +2466,52 @@ func init() { } var fileDescriptorContainers = []byte{ - // 738 bytes of a gzipped FileDescriptorProto + // 742 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcd, 0x6e, 0xd3, 0x4a, 0x14, 0xae, 0x93, 0xd4, 0x69, 0x4e, 0x36, 0x57, 0x73, 0x73, 0x73, 0x8d, 0x91, 0x92, 0x90, 0x55, - 0x16, 0x60, 0xd3, 0x80, 0xa0, 0x3f, 0xab, 0xa6, 0x7f, 0x42, 0x6a, 0x51, 0x35, 0xc0, 0x06, 0x16, - 0xc5, 0x49, 0x26, 0xa9, 0x89, 0xe3, 0x31, 0x9e, 0x49, 0xa4, 0x88, 0x05, 0x3c, 0x02, 0x6f, 0xc1, + 0x16, 0xe0, 0xd0, 0x80, 0xa0, 0x3f, 0xab, 0xa6, 0x7f, 0x42, 0x6a, 0x51, 0x35, 0xc0, 0x06, 0x16, + 0xc5, 0x49, 0x26, 0xa9, 0x89, 0xed, 0x31, 0x9e, 0x49, 0xa4, 0x88, 0x05, 0x3c, 0x02, 0x6f, 0xc1, 0x53, 0xb0, 0xef, 0x92, 0x25, 0xab, 0xd2, 0xe6, 0x49, 0x90, 0xc7, 0xe3, 0x3a, 0xe4, 0x47, 0x38, - 0x85, 0xee, 0xe6, 0x78, 0xce, 0xf7, 0x9d, 0x33, 0xdf, 0x7c, 0x67, 0x64, 0x38, 0xea, 0xda, 0xfc, - 0x6c, 0xd0, 0x34, 0x5a, 0xb4, 0x6f, 0xb6, 0xa8, 0xcb, 0x2d, 0xdb, 0x25, 0x7e, 0x7b, 0x72, 0x69, - 0x79, 0xb6, 0xc9, 0x88, 0x3f, 0xb4, 0x5b, 0x84, 0xc5, 0xdf, 0x99, 0x39, 0x5c, 0x9f, 0x88, 0x0c, - 0xcf, 0xa7, 0x9c, 0xa2, 0x7b, 0x31, 0xce, 0x88, 0x30, 0xc6, 0x44, 0xd6, 0x70, 0x5d, 0x2f, 0x74, - 0x69, 0x97, 0x8a, 0x6c, 0x33, 0x58, 0x85, 0x40, 0xfd, 0x4e, 0x97, 0xd2, 0xae, 0x43, 0x4c, 0x11, - 0x35, 0x07, 0x1d, 0xd3, 0x72, 0x47, 0x72, 0xeb, 0xee, 0xf4, 0x16, 0xe9, 0x7b, 0x3c, 0xda, 0xac, - 0x4c, 0x6f, 0x76, 0x6c, 0xe2, 0xb4, 0x4f, 0xfb, 0x16, 0xeb, 0xc9, 0x8c, 0xf2, 0x74, 0x06, 0xb7, - 0xfb, 0x84, 0x71, 0xab, 0xef, 0xc9, 0x84, 0xed, 0x44, 0x0a, 0xf0, 0x91, 0x47, 0x98, 0xd9, 0x26, - 0xac, 0xe5, 0xdb, 0x1e, 0xa7, 0x7e, 0x08, 0xae, 0x7e, 0xcd, 0x40, 0x6e, 0x37, 0xca, 0x44, 0x45, - 0x48, 0xd9, 0x6d, 0x4d, 0xa9, 0x28, 0xb5, 0x5c, 0x43, 0x1d, 0x5f, 0x94, 0x53, 0xcf, 0xf6, 0x70, - 0xca, 0x6e, 0xa3, 0x13, 0x50, 0x1d, 0xab, 0x49, 0x1c, 0xa6, 0xa5, 0x2a, 0xe9, 0x5a, 0xbe, 0xbe, - 0x61, 0xfc, 0x56, 0x27, 0xe3, 0x9a, 0xd5, 0x38, 0x12, 0xd0, 0x7d, 0x97, 0xfb, 0x23, 0x2c, 0x79, - 0x50, 0x01, 0x56, 0xed, 0xbe, 0xd5, 0x25, 0x5a, 0x3a, 0x28, 0x86, 0xc3, 0x00, 0x3d, 0x87, 0xac, - 0x3f, 0x70, 0x83, 0x03, 0x6a, 0x99, 0x8a, 0x52, 0xcb, 0xd7, 0x1f, 0x2f, 0x55, 0x08, 0x87, 0x58, - 0x1c, 0x91, 0xa0, 0x1a, 0x64, 0x98, 0x47, 0x5a, 0xda, 0xaa, 0x20, 0x2b, 0x18, 0xa1, 0x94, 0x46, - 0x24, 0xa5, 0xb1, 0xe3, 0x8e, 0xb0, 0xc8, 0x40, 0x55, 0x50, 0x7d, 0x4a, 0x79, 0x87, 0x69, 0xaa, - 0x38, 0x3d, 0x8c, 0x2f, 0xca, 0x2a, 0xa6, 0x94, 0x1f, 0xbc, 0xc0, 0x72, 0x07, 0xed, 0x02, 0xb4, - 0x7c, 0x62, 0x71, 0xd2, 0x3e, 0xb5, 0xb8, 0x96, 0x15, 0x9c, 0xfa, 0x0c, 0xe7, 0xcb, 0xe8, 0x7a, - 0x1a, 0x6b, 0xe7, 0x17, 0xe5, 0x95, 0xcf, 0x3f, 0xca, 0x0a, 0xce, 0x49, 0xdc, 0x0e, 0x0f, 0x48, - 0x06, 0x5e, 0x3b, 0x22, 0x59, 0x5b, 0x86, 0x44, 0xe2, 0x76, 0xb8, 0xbe, 0x09, 0xf9, 0x09, 0x51, - 0xd1, 0x3f, 0x90, 0xee, 0x91, 0x51, 0x78, 0x6f, 0x38, 0x58, 0x06, 0xf2, 0x0e, 0x2d, 0x67, 0x40, - 0xb4, 0x54, 0x28, 0xaf, 0x08, 0xb6, 0x52, 0x1b, 0x8a, 0x7e, 0x0c, 0x59, 0x29, 0x13, 0x42, 0x90, - 0x71, 0xad, 0x3e, 0x91, 0x38, 0xb1, 0x46, 0x06, 0x64, 0xa9, 0xc7, 0x6d, 0xea, 0x32, 0x01, 0x5d, + 0x85, 0xee, 0xe6, 0x78, 0xce, 0xf7, 0x9d, 0x33, 0xdf, 0x7c, 0x67, 0x64, 0x38, 0xea, 0x59, 0xfc, + 0x6c, 0xd0, 0x32, 0xda, 0xd4, 0xa9, 0xb7, 0xa9, 0xcb, 0x4d, 0xcb, 0x25, 0x7e, 0x67, 0x72, 0x69, + 0x7a, 0x56, 0x9d, 0x11, 0x7f, 0x68, 0xb5, 0x09, 0x8b, 0xbf, 0xb3, 0xfa, 0x70, 0x7d, 0x22, 0x32, + 0x3c, 0x9f, 0x72, 0x8a, 0xee, 0xc5, 0x38, 0x23, 0xc2, 0x18, 0x13, 0x59, 0xc3, 0x75, 0xbd, 0xd0, + 0xa3, 0x3d, 0x2a, 0xb2, 0xeb, 0xc1, 0x2a, 0x04, 0xea, 0x77, 0x7a, 0x94, 0xf6, 0x6c, 0x52, 0x17, + 0x51, 0x6b, 0xd0, 0xad, 0x9b, 0xee, 0x48, 0x6e, 0xdd, 0x9d, 0xde, 0x22, 0x8e, 0xc7, 0xa3, 0xcd, + 0xca, 0xf4, 0x66, 0xd7, 0x22, 0x76, 0xe7, 0xd4, 0x31, 0x59, 0x5f, 0x66, 0x94, 0xa7, 0x33, 0xb8, + 0xe5, 0x10, 0xc6, 0x4d, 0xc7, 0x93, 0x09, 0xdb, 0x89, 0x14, 0xe0, 0x23, 0x8f, 0xb0, 0x7a, 0x87, + 0xb0, 0xb6, 0x6f, 0x79, 0x9c, 0xfa, 0x21, 0xb8, 0xfa, 0x35, 0x03, 0xb9, 0xdd, 0x28, 0x13, 0x15, + 0x21, 0x65, 0x75, 0x34, 0xa5, 0xa2, 0xd4, 0x72, 0x4d, 0x75, 0x7c, 0x51, 0x4e, 0x3d, 0xdb, 0xc3, + 0x29, 0xab, 0x83, 0x4e, 0x40, 0xb5, 0xcd, 0x16, 0xb1, 0x99, 0x96, 0xaa, 0xa4, 0x6b, 0xf9, 0xc6, + 0x86, 0xf1, 0x5b, 0x9d, 0x8c, 0x6b, 0x56, 0xe3, 0x48, 0x40, 0xf7, 0x5d, 0xee, 0x8f, 0xb0, 0xe4, + 0x41, 0x05, 0x58, 0xb5, 0x1c, 0xb3, 0x47, 0xb4, 0x74, 0x50, 0x0c, 0x87, 0x01, 0x7a, 0x0e, 0x59, + 0x7f, 0xe0, 0x06, 0x07, 0xd4, 0x32, 0x15, 0xa5, 0x96, 0x6f, 0x3c, 0x5e, 0xaa, 0x10, 0x0e, 0xb1, + 0x38, 0x22, 0x41, 0x35, 0xc8, 0x30, 0x8f, 0xb4, 0xb5, 0x55, 0x41, 0x56, 0x30, 0x42, 0x29, 0x8d, + 0x48, 0x4a, 0x63, 0xc7, 0x1d, 0x61, 0x91, 0x81, 0xaa, 0xa0, 0xfa, 0x94, 0xf2, 0x2e, 0xd3, 0x54, + 0x71, 0x7a, 0x18, 0x5f, 0x94, 0x55, 0x4c, 0x29, 0x3f, 0x78, 0x81, 0xe5, 0x0e, 0xda, 0x05, 0x68, + 0xfb, 0xc4, 0xe4, 0xa4, 0x73, 0x6a, 0x72, 0x2d, 0x2b, 0x38, 0xf5, 0x19, 0xce, 0x97, 0xd1, 0xf5, + 0x34, 0xd7, 0xce, 0x2f, 0xca, 0x2b, 0x9f, 0x7f, 0x94, 0x15, 0x9c, 0x93, 0xb8, 0x1d, 0x1e, 0x90, + 0x0c, 0xbc, 0x4e, 0x44, 0xb2, 0xb6, 0x0c, 0x89, 0xc4, 0xed, 0x70, 0x7d, 0x13, 0xf2, 0x13, 0xa2, + 0xa2, 0x7f, 0x20, 0xdd, 0x27, 0xa3, 0xf0, 0xde, 0x70, 0xb0, 0x0c, 0xe4, 0x1d, 0x9a, 0xf6, 0x80, + 0x68, 0xa9, 0x50, 0x5e, 0x11, 0x6c, 0xa5, 0x36, 0x14, 0xfd, 0x18, 0xb2, 0x52, 0x26, 0x84, 0x20, + 0xe3, 0x9a, 0x0e, 0x91, 0x38, 0xb1, 0x46, 0x06, 0x64, 0xa9, 0xc7, 0x2d, 0xea, 0x32, 0x01, 0x5d, 0x24, 0x5a, 0x94, 0x54, 0x7d, 0x00, 0xff, 0x1e, 0x12, 0x7e, 0x7d, 0x05, 0x98, 0xbc, 0x1f, 0x10, 0xc6, 0x17, 0x19, 0xa9, 0x7a, 0x06, 0x85, 0x5f, 0xd3, 0x99, 0x47, 0x5d, 0x46, 0xd0, 0x09, 0xe4, - 0xae, 0x2f, 0x55, 0xc0, 0xf2, 0xf5, 0xfb, 0xcb, 0x5c, 0x7d, 0x23, 0x13, 0xc8, 0x84, 0x63, 0x92, - 0xaa, 0x09, 0xff, 0x1d, 0xd9, 0x2c, 0x2e, 0xc5, 0xe2, 0xd6, 0xd4, 0x8e, 0xed, 0x70, 0x59, 0x27, - 0x87, 0x65, 0x54, 0x75, 0xa0, 0x38, 0x0d, 0x90, 0xcd, 0x61, 0x80, 0xb8, 0xac, 0xa6, 0x88, 0x09, - 0xb8, 0x49, 0x77, 0x13, 0x2c, 0xd5, 0x77, 0x50, 0xdc, 0x15, 0x9e, 0x98, 0x91, 0xee, 0xef, 0x4b, - 0xd1, 0x83, 0xff, 0x67, 0x6a, 0xdd, 0x9a, 0xee, 0x5f, 0x14, 0x28, 0xbe, 0x12, 0x46, 0xbd, 0xfd, - 0x93, 0xa1, 0x6d, 0xc8, 0x87, 0x43, 0x21, 0x1e, 0x4c, 0xe9, 0xd8, 0xd9, 0x69, 0x3a, 0x08, 0xde, - 0xd4, 0x63, 0x8b, 0xf5, 0xb0, 0x9c, 0xbd, 0x60, 0x1d, 0xc8, 0x32, 0xd3, 0xe8, 0xad, 0xc9, 0xf2, - 0x10, 0x8a, 0x7b, 0xc4, 0x21, 0x73, 0x54, 0x59, 0x30, 0x2a, 0xf5, 0xcb, 0x0c, 0x40, 0x6c, 0x46, - 0x34, 0x84, 0xf4, 0x21, 0xe1, 0xe8, 0x49, 0x82, 0x36, 0xe6, 0x0c, 0xa4, 0xfe, 0x74, 0x69, 0x9c, - 0x94, 0xe2, 0x03, 0x64, 0x82, 0xb1, 0x40, 0x49, 0x9e, 0xfc, 0xb9, 0x03, 0xa7, 0x6f, 0xde, 0x00, - 0x29, 0x8b, 0x7f, 0x04, 0x35, 0x74, 0x2e, 0x4a, 0x42, 0x32, 0x7f, 0xa0, 0xf4, 0xad, 0x9b, 0x40, - 0xe3, 0x06, 0x42, 0x8f, 0x24, 0x6a, 0x60, 0xbe, 0xef, 0x13, 0x35, 0xb0, 0xc8, 0x89, 0x6f, 0x40, - 0x0d, 0x7d, 0x93, 0xa8, 0x81, 0xf9, 0x16, 0xd3, 0x8b, 0x33, 0x13, 0xb1, 0x1f, 0xfc, 0x82, 0x34, - 0xde, 0x9e, 0x5f, 0x95, 0x56, 0xbe, 0x5f, 0x95, 0x56, 0x3e, 0x8d, 0x4b, 0xca, 0xf9, 0xb8, 0xa4, - 0x7c, 0x1b, 0x97, 0x94, 0xcb, 0x71, 0x49, 0x79, 0x7d, 0xf0, 0x07, 0x7f, 0x55, 0xdb, 0x71, 0xd4, - 0x54, 0x45, 0xc5, 0x47, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x07, 0x2d, 0x75, 0x3c, 0xa6, 0x09, - 0x00, 0x00, + 0xae, 0x2f, 0x55, 0xc0, 0xf2, 0x8d, 0xfb, 0xcb, 0x5c, 0x7d, 0x33, 0x13, 0xc8, 0x84, 0x63, 0x92, + 0xea, 0x3a, 0xfc, 0x77, 0x64, 0xb1, 0xb8, 0x14, 0x8b, 0x5a, 0xd3, 0x20, 0xdb, 0xb5, 0x6c, 0x4e, + 0x7c, 0xa6, 0x29, 0x95, 0x74, 0x2d, 0x87, 0xa3, 0xb0, 0x6a, 0x43, 0x71, 0x1a, 0x22, 0xdb, 0xc3, + 0x00, 0x71, 0x61, 0x01, 0xbb, 0x59, 0x7f, 0x13, 0x2c, 0xd5, 0x77, 0x50, 0xdc, 0x15, 0xae, 0x98, + 0x11, 0xef, 0xef, 0x8b, 0xd1, 0x87, 0xff, 0x67, 0x6a, 0xdd, 0x9a, 0xf2, 0x5f, 0x14, 0x28, 0xbe, + 0x12, 0x56, 0xbd, 0xfd, 0x93, 0xa1, 0x6d, 0xc8, 0x87, 0x63, 0x21, 0x9e, 0x4c, 0xe9, 0xd9, 0xd9, + 0x79, 0x3a, 0x08, 0x5e, 0xd5, 0x63, 0x93, 0xf5, 0xb1, 0x9c, 0xbe, 0x60, 0x1d, 0xc8, 0x32, 0xd3, + 0xe8, 0xad, 0xc9, 0xf2, 0x10, 0x8a, 0x7b, 0xc4, 0x26, 0x73, 0x54, 0x59, 0x30, 0x2c, 0x8d, 0xcb, + 0x0c, 0x40, 0x6c, 0x46, 0x34, 0x84, 0xf4, 0x21, 0xe1, 0xe8, 0x49, 0x82, 0x36, 0xe6, 0x8c, 0xa4, + 0xfe, 0x74, 0x69, 0x9c, 0x94, 0xe2, 0x03, 0x64, 0x82, 0xb1, 0x40, 0x49, 0x1e, 0xfd, 0xb9, 0x23, + 0xa7, 0x6f, 0xde, 0x00, 0x29, 0x8b, 0x7f, 0x04, 0x35, 0x74, 0x2e, 0x4a, 0x42, 0x32, 0x7f, 0xa0, + 0xf4, 0xad, 0x9b, 0x40, 0xe3, 0x06, 0x42, 0x8f, 0x24, 0x6a, 0x60, 0xbe, 0xef, 0x13, 0x35, 0xb0, + 0xc8, 0x89, 0x6f, 0x40, 0x0d, 0x7d, 0x93, 0xa8, 0x81, 0xf9, 0x16, 0xd3, 0x8b, 0x33, 0x13, 0xb1, + 0x1f, 0xfc, 0x84, 0x34, 0xdf, 0x9e, 0x5f, 0x95, 0x56, 0xbe, 0x5f, 0x95, 0x56, 0x3e, 0x8d, 0x4b, + 0xca, 0xf9, 0xb8, 0xa4, 0x7c, 0x1b, 0x97, 0x94, 0xcb, 0x71, 0x49, 0x79, 0x7d, 0xf0, 0x07, 0xff, + 0x55, 0xdb, 0x71, 0xd4, 0x52, 0x45, 0xc5, 0x47, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x95, + 0xa9, 0x10, 0xa8, 0x09, 0x00, 0x00, } diff --git a/api/services/containers/v1/containers.proto b/api/services/containers/v1/containers.proto index 3fe0cae87..249abea47 100644 --- a/api/services/containers/v1/containers.proto +++ b/api/services/containers/v1/containers.proto @@ -91,7 +91,17 @@ message GetContainerResponse { } message ListContainersRequest { - string filter = 1; // TODO(stevvooe): Define a filtering syntax to make these queries. + // Filters contains one or more filters using the syntax defined in the + // containerd filter package. + // + // The returned result will be those that match any of the provided + // filters. Expanded, containers that match the following will be + // returned: + // + // filters[0] or filters[1] or ... or filters[n-1] or filters[n] + // + // If filters is zero-length or nil, all items will be returned. + repeated string filters = 1; } message ListContainersResponse { diff --git a/client.go b/client.go index 45d078a70..ce947cdbb 100644 --- a/client.go +++ b/client.go @@ -127,8 +127,10 @@ func (c *Client) IsServing(ctx context.Context) (bool, error) { } // Containers returns all containers created in containerd -func (c *Client) Containers(ctx context.Context) ([]Container, error) { - r, err := c.ContainerService().List(ctx, &containers.ListContainersRequest{}) +func (c *Client) Containers(ctx context.Context, filters ...string) ([]Container, error) { + r, err := c.ContainerService().List(ctx, &containers.ListContainersRequest{ + Filters: filters, + }) if err != nil { return nil, err } diff --git a/cmd/ctr/labels.go b/cmd/ctr/labels.go new file mode 100644 index 000000000..448db1928 --- /dev/null +++ b/cmd/ctr/labels.go @@ -0,0 +1,52 @@ +package main + +import ( + "errors" + "fmt" + "strings" + + "github.com/urfave/cli" +) + +var containersSetLabelsCommand = cli.Command{ + Name: "set-labels", + Usage: "Set and clear labels for a container.", + ArgsUsage: "[flags] [=, ...]", + Description: "Set and clear labels for a container.", + Flags: []cli.Flag{}, + Action: func(clicontext *cli.Context) error { + var ( + ctx, cancel = appContext(clicontext) + containerID, labels = objectWithLabelArgs(clicontext) + ) + defer cancel() + + client, err := newClient(clicontext) + if err != nil { + return err + } + + if containerID == "" { + return errors.New("please specify a container") + } + + container, err := client.LoadContainer(ctx, containerID) + if err != nil { + return err + } + + setlabels, err := container.SetLabels(ctx, labels) + if err != nil { + return err + } + + var labelStrings []string + for k, v := range setlabels { + labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v)) + } + + fmt.Println(strings.Join(labelStrings, ",")) + + return nil + }, +} diff --git a/cmd/ctr/list.go b/cmd/ctr/list.go index 845b7ea7c..29893bbff 100644 --- a/cmd/ctr/list.go +++ b/cmd/ctr/list.go @@ -3,11 +3,11 @@ package main import ( "fmt" "os" + "strings" "text/tabwriter" "github.com/containerd/containerd" tasks "github.com/containerd/containerd/api/services/tasks/v1" - tasktypes "github.com/containerd/containerd/api/types/task" "github.com/urfave/cli" ) @@ -31,24 +31,18 @@ var taskListCommand = cli.Command{ }, } -var containerListCommand = cli.Command{ - Name: "containers", - Usage: "manage containers (metadata)", - Aliases: []string{"c"}, - Subcommands: []cli.Command{ - { - Name: "list", - Usage: "list tasks", - Aliases: []string{"ls"}, - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "quiet, q", - Usage: "print only the container id", - }, - }, - Action: containerListFn, +var containersListCommand = cli.Command{ + Name: "list", + Usage: "list all tasks or those that match a filter", + ArgsUsage: "[filter, ...]", + Aliases: []string{"ls"}, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "quiet, q", + Usage: "print only the container id", }, }, + Action: containerListFn, } func taskListFn(context *cli.Context) error { @@ -62,66 +56,25 @@ func taskListFn(context *cli.Context) error { if err != nil { return err } - containers, err := client.Containers(ctx) - if err != nil { - return err - } s := client.TaskService() - - tasksResponse, err := s.List(ctx, &tasks.ListTasksRequest{}) + response, err := s.List(ctx, &tasks.ListTasksRequest{}) if err != nil { return err } - // Join with tasks to get status. - tasksByContainerID := map[string]*tasktypes.Task{} - for _, task := range tasksResponse.Tasks { - tasksByContainerID[task.ContainerID] = task - } - if quiet { - for _, c := range containers { - task, ok := tasksByContainerID[c.ID()] - if ok { - fmt.Printf("%s\t%d\n", c.ID(), task.Pid) - } else { - //Since task is not running, PID is printed 0 - fmt.Printf("%s\t%d\n", c.ID(), 0) - } + for _, task := range response.Tasks { + fmt.Println(task.ID) } } else { - - w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0) - fmt.Fprintln(w, "TASK-ID\tIMAGE\tPID\tSTATUS") - for _, c := range containers { - var imageName string - if image, err := c.Image(ctx); err != nil { - if err != containerd.ErrNoImage { - return err - } - imageName = "-" - } else { - imageName = image.Name() - } - var ( - status string - pid uint32 - ) - task, ok := tasksByContainerID[c.ID()] - if ok { - status = task.Status.String() - pid = task.Pid - } else { - status = "STOPPED" // TODO(stevvooe): Is this assumption correct? - pid = 0 - } - - if _, err := fmt.Fprintf(w, "%s\t%s\t%d\t%s\n", - c.ID(), - imageName, - pid, - status, + w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0) + fmt.Fprintln(w, "TASK\tPID\tSTATUS\t") + for _, task := range response.Tasks { + if _, err := fmt.Fprintf(w, "%s\t%d\t%s\n", + task.ID, + task.Pid, + task.Status.String(), ); err != nil { return err } @@ -133,6 +86,7 @@ func taskListFn(context *cli.Context) error { func containerListFn(context *cli.Context) error { var ( + filters = context.Args() quiet = context.Bool("quiet") ctx, cancel = appContext(context) ) @@ -142,18 +96,28 @@ func containerListFn(context *cli.Context) error { if err != nil { return err } - containers, err := client.Containers(ctx) + containers, err := client.Containers(ctx, filters...) if err != nil { return err } + if quiet { for _, c := range containers { fmt.Printf("%s\n", c.ID()) } } else { - cl := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0) - fmt.Fprintln(cl, "ID\tIMAGE\tRUNTIME\tSIZE") + w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0) + fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\tLABELS\t") for _, c := range containers { + var labelStrings []string + for k, v := range c.Proto().Labels { + labelStrings = append(labelStrings, strings.Join([]string{k, v}, "=")) + } + labels := strings.Join(labelStrings, ",") + if labels == "" { + labels = "-" + } + var imageName string if image, err := c.Image(ctx); err != nil { if err != containerd.ErrNoImage { @@ -163,17 +127,18 @@ func containerListFn(context *cli.Context) error { } else { imageName = image.Name() } + proto := c.Proto() - if _, err := fmt.Fprintf(cl, "%s\t%s\t%s\t%d\n", + if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t\n", c.ID(), imageName, proto.Runtime.Name, - proto.Size(), + labels, ); err != nil { return err } } - return cl.Flush() + return w.Flush() } return nil } diff --git a/cmd/ctr/main.go b/cmd/ctr/main.go index 76a2c7c21..0ae139fce 100644 --- a/cmd/ctr/main.go +++ b/cmd/ctr/main.go @@ -60,7 +60,7 @@ containerd CLI deleteCommand, namespacesCommand, eventsCommand, - containerListCommand, + containersCommand, taskListCommand, infoCommand, killCommand, @@ -83,3 +83,13 @@ containerd CLI os.Exit(1) } } + +var containersCommand = cli.Command{ + Name: "containers", + Usage: "manage containers (metadata)", + Aliases: []string{"c"}, + Subcommands: []cli.Command{ + containersListCommand, + containersSetLabelsCommand, + }, +} diff --git a/cmd/ctr/namespaces.go b/cmd/ctr/namespaces.go index c86255391..fd29cc59b 100644 --- a/cmd/ctr/namespaces.go +++ b/cmd/ctr/namespaces.go @@ -33,7 +33,7 @@ var namespacesCreateCommand = cli.Command{ Action: func(clicontext *cli.Context) error { var ( ctx = context.Background() - namespace, labels = namespaceWithLabelArgs(clicontext) + namespace, labels = objectWithLabelArgs(clicontext) ) if namespace == "" { @@ -53,27 +53,6 @@ var namespacesCreateCommand = cli.Command{ }, } -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.", @@ -83,7 +62,7 @@ var namespacesSetLabelsCommand = cli.Command{ Action: func(clicontext *cli.Context) error { var ( ctx = context.Background() - namespace, labels = namespaceWithLabelArgs(clicontext) + namespace, labels = objectWithLabelArgs(clicontext) ) namespaces, err := getNamespacesService(clicontext) diff --git a/cmd/ctr/run.go b/cmd/ctr/run.go index 829e69c7a..dd5b7f7d1 100644 --- a/cmd/ctr/run.go +++ b/cmd/ctr/run.go @@ -74,6 +74,10 @@ var runCommand = cli.Command{ Name: "env", Usage: "specify additional container environment variables (i.e. FOO=bar)", }, + cli.StringSliceFlag{ + Name: "label", + Usage: "specify additional labels (foo=bar)", + }, cli.BoolFlag{ Name: "rm", Usage: "remove the container after running", diff --git a/cmd/ctr/run_unix.go b/cmd/ctr/run_unix.go index df6d040cd..1c7a7310c 100644 --- a/cmd/ctr/run_unix.go +++ b/cmd/ctr/run_unix.go @@ -57,11 +57,15 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli err error checkpointIndex digest.Digest - ref = context.Args().First() - id = context.Args().Get(1) - args = context.Args()[2:] - tty = context.Bool("tty") + ref = context.Args().First() + id = context.Args().Get(1) + args = context.Args()[2:] + tty = context.Bool("tty") + labelStrings = context.StringSlice("label") ) + + labels := labelArgs(labelStrings) + if raw := context.String("checkpoint"); raw != "" { if checkpointIndex, err = digest.Parse(raw); err != nil { return nil, err @@ -71,6 +75,7 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli if err != nil { return nil, err } + if checkpointIndex == "" { opts := []containerd.SpecOpts{ containerd.WithImageConfig(ctx, image), @@ -96,12 +101,15 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli } else { rootfs = containerd.WithNewRootFS(id, image) } + return client.NewContainer(ctx, id, containerd.WithSpec(spec), containerd.WithImage(image), + containerd.WithContainerLabels(labels), rootfs, ) } + return client.NewContainer(ctx, id, containerd.WithCheckpoint(v1.Descriptor{ Digest: checkpointIndex, }, id)) diff --git a/cmd/ctr/utils.go b/cmd/ctr/utils.go index bb26cd86d..50e58dc05 100644 --- a/cmd/ctr/utils.go +++ b/cmd/ctr/utils.go @@ -247,3 +247,28 @@ func replaceOrAppendEnvValues(defaults, overrides []string) []string { return defaults } + +func objectWithLabelArgs(clicontext *cli.Context) (string, map[string]string) { + var ( + namespace = clicontext.Args().First() + labelStrings = clicontext.Args().Tail() + ) + + return namespace, labelArgs(labelStrings) +} + +func labelArgs(labelStrings []string) map[string]string { + 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 labels +} diff --git a/container.go b/container.go index 73e487fcd..57347a3d8 100644 --- a/container.go +++ b/container.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "path/filepath" + "strings" "sync" "google.golang.org/grpc" @@ -12,6 +13,7 @@ import ( "github.com/containerd/containerd/api/services/containers/v1" "github.com/containerd/containerd/api/services/tasks/v1" "github.com/containerd/containerd/api/types" + ptypes "github.com/gogo/protobuf/types" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -33,6 +35,8 @@ type Container interface { Spec() (*specs.Spec, error) Task(context.Context, IOAttach) (Task, error) Image(context.Context) (Image, error) + Labels(context.Context) (map[string]string, error) + SetLabels(context.Context, map[string]string) (map[string]string, error) } func containerFromProto(client *Client, c containers.Container) *container { @@ -60,6 +64,53 @@ func (c *container) Proto() containers.Container { return c.c } +func (c *container) Labels(ctx context.Context) (map[string]string, error) { + resp, err := c.client.ContainerService().Get(ctx, &containers.GetContainerRequest{ + ID: c.ID(), + }) + if err != nil { + return nil, err + } + + c.c = resp.Container + + m := make(map[string]string, len(resp.Container.Labels)) + for k, v := range c.c.Labels { + m[k] = v + } + + return m, nil +} + +func (c *container) SetLabels(ctx context.Context, labels map[string]string) (map[string]string, error) { + var req containers.UpdateContainerRequest + + req.Container.ID = c.ID() + req.Container.Labels = labels + + req.UpdateMask = &ptypes.FieldMask{ + Paths: make([]string, 0, len(labels)), + } + // mask off paths so we only muck with the labels encountered in labels. + // Labels not in the passed in argument will be left alone. + for k := range labels { + req.UpdateMask.Paths = append(req.UpdateMask.Paths, strings.Join([]string{"labels", k}, ".")) + } + + resp, err := c.client.ContainerService().Update(ctx, &req) + if err != nil { + return nil, err + } + + c.c = resp.Container // update our local container + + m := make(map[string]string, len(resp.Container.Labels)) + for k, v := range c.c.Labels { + m[k] = v + } + return m, nil +} + // Spec returns the current OCI specification for the container func (c *container) Spec() (*specs.Spec, error) { var s specs.Spec diff --git a/containers/containers.go b/containers/containers.go index 06ba37dd8..845da15f4 100644 --- a/containers/containers.go +++ b/containers/containers.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/containerd/containerd/filters" "github.com/gogo/protobuf/types" ) @@ -29,7 +30,10 @@ type RuntimeInfo struct { type Store interface { Get(ctx context.Context, id string) (Container, error) - List(ctx context.Context, filter string) ([]Container, error) + + // List returns containers that match one or more of the provided filters. + List(ctx context.Context, filters ...filters.Filter) ([]Container, error) + Create(ctx context.Context, container Container) (Container, error) Update(ctx context.Context, container Container) (Container, error) Delete(ctx context.Context, id string) error diff --git a/metadata/containers.go b/metadata/containers.go index 90257cdfd..221ed4751 100644 --- a/metadata/containers.go +++ b/metadata/containers.go @@ -2,11 +2,13 @@ package metadata import ( "context" + "strings" "time" "github.com/boltdb/bolt" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/filters" "github.com/containerd/containerd/identifiers" "github.com/containerd/containerd/namespaces" "github.com/gogo/protobuf/proto" @@ -43,16 +45,22 @@ func (s *containerStore) Get(ctx context.Context, id string) (containers.Contain return container, nil } -func (s *containerStore) List(ctx context.Context, filter string) ([]containers.Container, error) { +func (s *containerStore) List(ctx context.Context, fs ...filters.Filter) ([]containers.Container, error) { namespace, err := namespaces.NamespaceRequired(ctx) if err != nil { return nil, err } var ( - m []containers.Container - bkt = getContainersBucket(s.tx, namespace) + m []containers.Container + filter = filters.Filter(filters.Any(fs)) + bkt = getContainersBucket(s.tx, namespace) ) + + if len(fs) == 0 { + filter = filters.Always + } + if bkt == nil { return m, nil } @@ -66,7 +74,10 @@ func (s *containerStore) List(ctx context.Context, filter string) ([]containers. if err := readContainer(&container, cbkt); err != nil { return errors.Wrap(err, "failed to read container") } - m = append(m, container) + + if filter.Match(adaptContainer(container)) { + m = append(m, container) + } return nil }); err != nil { return nil, err @@ -75,6 +86,46 @@ func (s *containerStore) List(ctx context.Context, filter string) ([]containers. return m, nil } +func adaptContainer(o interface{}) filters.Adaptor { + obj := o.(containers.Container) + return filters.AdapterFunc(func(fieldpath []string) (string, bool) { + if len(fieldpath) == 0 { + return "", false + } + + switch fieldpath[0] { + case "id": + return obj.ID, len(obj.ID) > 0 + case "runtime": + if len(fieldpath) <= 1 { + return "", false + } + + switch fieldpath[1] { + case "name": + return obj.Runtime.Name, len(obj.Runtime.Name) > 0 + default: + return "", false + } + case "image": + return obj.Image, len(obj.Image) > 0 + case "labels": + return checkMap(fieldpath[1:], obj.Labels) + } + + return "", false + }) +} + +func checkMap(fieldpath []string, m map[string]string) (string, bool) { + if len(m) == 0 { + return "", false + } + + value, ok := m[strings.Join(fieldpath, ".")] + return value, ok +} + func (s *containerStore) Create(ctx context.Context, container containers.Container) (containers.Container, error) { namespace, err := namespaces.NamespaceRequired(ctx) if err != nil { @@ -268,14 +319,22 @@ func writeContainer(container *containers.Container, bkt *bolt.Bucket) error { return err } } + lbkt, err := bkt.CreateBucket(bucketKeyLabels) if err != nil { return err } + for k, v := range container.Labels { + if v == "" { + delete(container.Labels, k) // remove since we don't actually set it + continue + } + if err := lbkt.Put([]byte(k), []byte(v)); err != nil { return err } } + return nil } diff --git a/metadata/namespaces.go b/metadata/namespaces.go index 1b3c2ef1b..5aa19b3fe 100644 --- a/metadata/namespaces.go +++ b/metadata/namespaces.go @@ -136,7 +136,7 @@ func (s *namespaceStore) namespaceEmpty(ctx context.Context, namespace string) ( } containerStore := NewContainerStore(s.tx) - containers, err := containerStore.List(ctx, "") + containers, err := containerStore.List(ctx) if err != nil { return false, err } diff --git a/services/containers/helpers.go b/services/containers/helpers.go index 7d84fc4e7..843742c14 100644 --- a/services/containers/helpers.go +++ b/services/containers/helpers.go @@ -35,15 +35,25 @@ func containerToProto(container *containers.Container) api.Container { } func containerFromProto(containerpb *api.Container) containers.Container { - return containers.Container{ - ID: containerpb.ID, - Labels: containerpb.Labels, - Image: containerpb.Image, - Runtime: containers.RuntimeInfo{ + var runtime containers.RuntimeInfo + if containerpb.Runtime != nil { + runtime = containers.RuntimeInfo{ Name: containerpb.Runtime.Name, Options: containerpb.Runtime.Options, - }, - Spec: containerpb.Spec.Value, - RootFS: containerpb.RootFS, + } + } + + var spec []byte + if containerpb.Spec != nil { + spec = containerpb.Spec.Value + } + + return containers.Container{ + ID: containerpb.ID, + Labels: containerpb.Labels, + Image: containerpb.Image, + Runtime: runtime, + Spec: spec, + RootFS: containerpb.RootFS, } } diff --git a/services/containers/service.go b/services/containers/service.go index 567d52eac..7208d14fc 100644 --- a/services/containers/service.go +++ b/services/containers/service.go @@ -1,12 +1,15 @@ package containers import ( + "strings" + "github.com/boltdb/bolt" api "github.com/containerd/containerd/api/services/containers/v1" eventsapi "github.com/containerd/containerd/api/services/events/v1" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/events" + "github.com/containerd/containerd/filters" "github.com/containerd/containerd/metadata" "github.com/containerd/containerd/plugin" "github.com/golang/protobuf/ptypes/empty" @@ -65,8 +68,18 @@ func (s *Service) Get(ctx context.Context, req *api.GetContainerRequest) (*api.G func (s *Service) List(ctx context.Context, req *api.ListContainersRequest) (*api.ListContainersResponse, error) { var resp api.ListContainersResponse + var fs []filters.Filter + for _, s := range req.Filters { + f, err := filters.Parse(s) + if err != nil { + return nil, grpc.Errorf(codes.InvalidArgument, err.Error()) + } + + fs = append(fs, f) + } + return &resp, errdefs.ToGRPC(s.withStoreView(ctx, func(ctx context.Context, store containers.Store) error { - containers, err := store.List(ctx, req.Filter) + containers, err := store.List(ctx, fs...) if err != nil { return err } @@ -108,18 +121,20 @@ func (s *Service) Create(ctx context.Context, req *api.CreateContainerRequest) ( } func (s *Service) Update(ctx context.Context, req *api.UpdateContainerRequest) (*api.UpdateContainerResponse, error) { - var resp api.UpdateContainerResponse + var ( + resp api.UpdateContainerResponse + incoming = containerFromProto(&req.Container) + ) if err := s.withStoreUpdate(ctx, func(ctx context.Context, store containers.Store) error { - container := containerFromProto(&req.Container) - current, err := store.Get(ctx, container.ID) + container, err := store.Get(ctx, incoming.ID) if err != nil { return err } - if current.ID != container.ID { - return grpc.Errorf(codes.InvalidArgument, "container ids must match: %v != %v", current.ID, container.ID) + if container.ID != incoming.ID { + return grpc.Errorf(codes.InvalidArgument, "container ids must match: %v != %v", container.ID, incoming.ID) } // apply the field mask. If you update this code, you better follow the @@ -127,34 +142,39 @@ func (s *Service) Update(ctx context.Context, req *api.UpdateContainerRequest) ( // is, do not update this code. if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 { for _, path := range req.UpdateMask.Paths { + if strings.HasPrefix(path, "labels.") { + key := strings.TrimPrefix(path, "labels.") + container.Labels[key] = incoming.Labels[key] + continue + } + switch path { case "labels": - current.Labels = container.Labels + container.Labels = incoming.Labels case "image": - current.Image = container.Image + container.Image = incoming.Image case "runtime": // TODO(stevvooe): Should this actually be allowed? - current.Runtime = container.Runtime + container.Runtime = incoming.Runtime case "spec": - current.Spec = container.Spec + container.Spec = incoming.Spec case "rootfs": - current.RootFS = container.RootFS + container.RootFS = incoming.RootFS default: return grpc.Errorf(codes.InvalidArgument, "cannot update %q field", path) } } } else { // no field mask present, just replace everything - current = container + container = incoming } - created, err := store.Update(ctx, container) + updated, err := store.Update(ctx, container) if err != nil { return err } - resp.Container = containerToProto(&created) - + resp.Container = containerToProto(&updated) return nil }); err != nil { return &resp, errdefs.ToGRPC(err)