diff --git a/azure/backend.go b/azure/backend.go index 836e59ed4..35bbc5d48 100644 --- a/azure/backend.go +++ b/azure/backend.go @@ -32,9 +32,6 @@ const singleContainerName = "single--container--aci" // ErrNoSuchContainer is returned when the mentioned container does not exist var ErrNoSuchContainer = errors.New("no such container") -// ErrTooManyContainers is returned when trying to inspect on multiple containers at once -var ErrTooManyContainers = errors.New("more than one container in group ID") - func init() { backend.Register("aci", "aci", service, getCloudService) } @@ -245,7 +242,9 @@ func (cs *aciContainerService) Delete(ctx context.Context, containerID string, _ } func (cs *aciContainerService) Inspect(ctx context.Context, containerID string) (containers.Container, error) { - cg, err := getACIContainerGroup(ctx, cs.ctx, containerID) + groupName, containerName := getGroupAndContainerName(containerID) + + cg, err := getACIContainerGroup(ctx, cs.ctx, groupName) if err != nil { return containers.Container{}, err } @@ -253,48 +252,20 @@ func (cs *aciContainerService) Inspect(ctx context.Context, containerID string) return containers.Container{}, ErrNoSuchContainer } - if cg.Containers != nil && len(*cg.Containers) > 1 { - return containers.Container{}, ErrTooManyContainers + var cc containerinstance.Container + var found = false + for _, c := range *cg.Containers { + if to.String(c.Name) == containerName { + cc = c + found = true + break + } + } + if !found { + return containers.Container{}, ErrNoSuchContainer } - return containerGroupToContainer(cg) -} - -func containerGroupToContainer(cg containerinstance.ContainerGroup) (containers.Container, error) { - status := "unavailable" - cc := (*cg.Containers)[0] - if cc.InstanceView != nil && - cc.InstanceView.CurrentState != nil && - cc.InstanceView.CurrentState.State != nil { - status = to.String(cc.InstanceView.CurrentState.State) - } - - memLimits := -1. - if cc.Resources != nil && - cc.Resources.Limits != nil && - cc.Resources.Limits.MemoryInGB != nil { - memLimits = *cc.Resources.Limits.MemoryInGB - } - - command := "" - if cc.Command != nil { - command = strings.Join(*cc.Command, "") - } - c := containers.Container{ - ID: to.String(cg.Name), - Status: status, - Image: to.String(cc.Image), - Command: command, - CPUTime: 0, - MemoryUsage: 0, - MemoryLimit: uint64(memLimits), - PidsCurrent: 0, - PidsLimit: 0, - Labels: nil, - Ports: convert.ToPorts(cg.IPAddress, *cc.Ports), - } - - return c, nil + return convert.ContainerGroupToContainer(containerID, cg, cc) } type aciComposeService struct { diff --git a/azure/backend_test.go b/azure/backend_test.go index ef2ab4d10..6f014aa18 100644 --- a/azure/backend_test.go +++ b/azure/backend_test.go @@ -3,9 +3,14 @@ package azure import ( "testing" + "github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance" + "github.com/Azure/go-autorest/autorest/to" "github.com/stretchr/testify/suite" . "github.com/onsi/gomega" + + "github.com/docker/api/azure/convert" + "github.com/docker/api/containers" ) type BackendSuiteTest struct { @@ -30,3 +35,56 @@ func TestBackendSuite(t *testing.T) { RegisterTestingT(t) suite.Run(t, new(BackendSuiteTest)) } + +func TestContainerGroupToContainer(t *testing.T) { + myContainerGroup := containerinstance.ContainerGroup{ + ContainerGroupProperties: &containerinstance.ContainerGroupProperties{ + IPAddress: &containerinstance.IPAddress{ + Ports: &[]containerinstance.Port{{ + Port: to.Int32Ptr(80), + }}, + IP: to.StringPtr("42.42.42.42"), + }, + }, + } + myContainer := containerinstance.Container{ + Name: to.StringPtr("myContainerID"), + ContainerProperties: &containerinstance.ContainerProperties{ + Image: to.StringPtr("sha256:666"), + Command: to.StringSlicePtr([]string{"mycommand"}), + Ports: &[]containerinstance.ContainerPort{{ + Port: to.Int32Ptr(80), + }}, + EnvironmentVariables: nil, + InstanceView: &containerinstance.ContainerPropertiesInstanceView{ + RestartCount: nil, + CurrentState: &containerinstance.ContainerState{ + State: to.StringPtr("Running"), + }, + }, + Resources: &containerinstance.ResourceRequirements{ + Limits: &containerinstance.ResourceLimits{ + MemoryInGB: to.Float64Ptr(9), + }, + }, + }, + } + + var expectedContainer = containers.Container{ + ID: "myContainerID", + Status: "Running", + Image: "sha256:666", + Command: "mycommand", + MemoryLimit: 9, + Ports: []containers.Port{{ + HostPort: uint32(80), + ContainerPort: uint32(80), + Protocol: "tcp", + HostIP: "42.42.42.42", + }}, + } + + container, err := convert.ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer) + Expect(err).To(BeNil()) + Expect(container).To(Equal(expectedContainer)) +} diff --git a/azure/convert/convert.go b/azure/convert/convert.go index 223880879..1ff9b413d 100644 --- a/azure/convert/convert.go +++ b/azure/convert/convert.go @@ -12,6 +12,7 @@ import ( "github.com/compose-spec/compose-go/types" "github.com/docker/api/compose" + "github.com/docker/api/containers" "github.com/docker/api/context/store" ) @@ -226,3 +227,38 @@ func (s serviceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (c }, nil } + +func ContainerGroupToContainer(containerID string, cg containerinstance.ContainerGroup, cc containerinstance.Container) (containers.Container, error) { + memLimits := -1. + if cc.Resources != nil && + cc.Resources.Limits != nil && + cc.Resources.Limits.MemoryInGB != nil { + memLimits = *cc.Resources.Limits.MemoryInGB + } + + command := "" + if cc.Command != nil { + command = strings.Join(*cc.Command, " ") + } + + status := "Unknown" + if cc.InstanceView != nil && cc.InstanceView.CurrentState != nil { + status = *cc.InstanceView.CurrentState.State + } + + c := containers.Container{ + ID: containerID, + Status: status, + Image: to.String(cc.Image), + Command: command, + CPUTime: 0, + MemoryUsage: 0, + MemoryLimit: uint64(memLimits), + PidsCurrent: 0, + PidsLimit: 0, + Labels: nil, + Ports: ToPorts(cg.IPAddress, *cc.Ports), + } + + return c, nil +} diff --git a/cli/cmd/inspect.go b/cli/cmd/inspect.go index ef5fd4566..48fd1674d 100644 --- a/cli/cmd/inspect.go +++ b/cli/cmd/inspect.go @@ -11,7 +11,7 @@ import ( "github.com/docker/api/client" ) -// RmCommand deletes containers +// InspectCommand inspects into containers func InspectCommand() *cobra.Command { cmd := &cobra.Command{ Use: "inspect", diff --git a/cli/cmd/inspect_test.go b/cli/cmd/inspect_test.go new file mode 100644 index 000000000..cb4986b2a --- /dev/null +++ b/cli/cmd/inspect_test.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "gotest.tools/v3/golden" + + _ "github.com/docker/api/example" + "github.com/docker/api/tests/framework" +) + +type InspectSuite struct { + framework.CliSuite +} + +func (sut *InspectSuite) TestInspectId() { + err := runInspect(sut.Context(), "id") + require.Nil(sut.T(), err) + golden.Assert(sut.T(), sut.GetStdOut(), "inspect-out-id.golden") +} + +func TestInspect(t *testing.T) { + suite.Run(t, new(InspectSuite)) +} diff --git a/cli/cmd/testdata/inspect-out-id.golden b/cli/cmd/testdata/inspect-out-id.golden new file mode 100644 index 000000000..28473f4df --- /dev/null +++ b/cli/cmd/testdata/inspect-out-id.golden @@ -0,0 +1,13 @@ +{ + "ID": "id", + "Status": "", + "Image": "nginx", + "Command": "", + "CPUTime": 0, + "MemoryUsage": 0, + "MemoryLimit": 0, + "PidsCurrent": 0, + "PidsLimit": 0, + "Labels": null, + "Ports": null +} diff --git a/go.sum b/go.sum index 935e32bef..022fdbf52 100644 --- a/go.sum +++ b/go.sum @@ -4,9 +4,6 @@ github.com/AlecAivazis/survey/v2 v2.0.7 h1:+f825XHLse/hWd2tE/V5df04WFGimk34Eyg/z github.com/AlecAivazis/survey/v2 v2.0.7/go.mod h1:mlizQTaPjnR4jcpwRSaSlkbsRfYFEyKgLQvYTzxxiHA= github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZOMdj5HYo= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= -github.com/Azure/azure-sdk-for-go v0.2.0-beta h1:wYBqYNMWr0WL2lcEZi+dlK9n+N0wJ0Pjs4BKeOnDjfQ= -github.com/Azure/azure-sdk-for-go v42.3.0+incompatible h1:PAHkmPqd/vQV4LJcqzEUM1elCyTMWjbrO8oFMl0dvBE= -github.com/Azure/azure-sdk-for-go v42.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v43.1.0+incompatible h1:m6EAp2Dmb8/t+ToZ2jtmvdp+JBwsdfSlZuBV31WGLGQ= github.com/Azure/azure-sdk-for-go v43.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-storage-file-go v0.7.0 h1:yWoV0MYwzmoSgWACcVkdPolvAULFPNamcQLpIvS/Et4= @@ -208,16 +205,16 @@ github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -323,8 +320,6 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= -golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= @@ -366,7 +361,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/local/backend.go b/local/backend.go index b1fb4283a..ea2ccd4a1 100644 --- a/local/backend.go +++ b/local/backend.go @@ -72,7 +72,7 @@ func (ms *local) Inspect(ctx context.Context, id string) (containers.Container, return containers.Container{ ID: stringid.TruncateID(c.ID), Status: status, - Image: c.Path + "@" + c.Image, + Image: c.Image, Command: command, }, nil } diff --git a/local/e2e/backend_test.go b/local/e2e/backend_test.go index c719060a3..8c6c085b4 100644 --- a/local/e2e/backend_test.go +++ b/local/e2e/backend_test.go @@ -49,6 +49,14 @@ func (m *LocalBackendTestSuite) TestRunWithPorts() { m.NewDockerCommand("rm", "-f", "nginx").ExecOrDie() }() assert.Contains(m.T(), out, "8080") + + out = m.NewDockerCommand("inspect", "nginx").ExecOrDie() + assert.Contains(m.T(), out, "\"Status\": \"running\"") +} + +func (m *LocalBackendTestSuite) TestInspectNotFound() { + out, _ := m.NewDockerCommand("inspect", "nonexistentcontainer").Exec() + assert.Contains(m.T(), out, "Error: No such container: nonexistentcontainer") } func TestLocalBackendTestSuite(t *testing.T) { diff --git a/protos/containers/v1/containers.proto b/protos/containers/v1/containers.proto index 1748415eb..543387354 100644 --- a/protos/containers/v1/containers.proto +++ b/protos/containers/v1/containers.proto @@ -32,97 +32,97 @@ package com.docker.api.protos.containers.v1; option go_package = "github.com/docker/api/protos/containers/v1;v1"; service Containers { - rpc List(ListRequest) returns (ListResponse); - rpc Stop(StopRequest) returns (StopResponse); - rpc Run(RunRequest) returns (RunResponse); - rpc Exec(ExecRequest) returns (ExecResponse); - rpc Logs(LogsRequest) returns (stream LogsResponse); - rpc Delete(DeleteRequest) returns (DeleteResponse); - rpc Inspect(InspectRequest) returns (InspectResponse); + rpc List(ListRequest) returns (ListResponse); + rpc Stop(StopRequest) returns (StopResponse); + rpc Run(RunRequest) returns (RunResponse); + rpc Exec(ExecRequest) returns (ExecResponse); + rpc Logs(LogsRequest) returns (stream LogsResponse); + rpc Delete(DeleteRequest) returns (DeleteResponse); + rpc Inspect(InspectRequest) returns (InspectResponse); } message Port { - uint32 host_port = 1; - uint32 container_port = 2; - string protocol = 3; - string host_ip = 4; + uint32 host_port = 1; + uint32 container_port = 2; + string protocol = 3; + string host_ip = 4; } message Container { - string id = 1; - string image = 2; - string status = 3; - string command = 4; - uint64 cpu_time = 5; - uint64 memory_usage = 6; - uint64 memory_limit = 7; - uint64 pids_current = 8; - uint64 pids_limit = 9; - repeated string labels = 10; - repeated Port ports = 11; + string id = 1; + string image = 2; + string status = 3; + string command = 4; + uint64 cpu_time = 5; + uint64 memory_usage = 6; + uint64 memory_limit = 7; + uint64 pids_current = 8; + uint64 pids_limit = 9; + repeated string labels = 10; + repeated Port ports = 11; } message InspectRequest { - string id = 1; + string id = 1; } message InspectResponse { - Container container = 1; + Container container = 1; } message DeleteRequest { - string id = 1; - bool force = 2; + string id = 1; + bool force = 2; } message DeleteResponse { } message StopRequest { - string id = 1; - uint32 timeout = 2; + string id = 1; + uint32 timeout = 2; } message StopResponse { } message RunRequest { - string id = 1; - string image = 2; - repeated Port ports = 3; - map labels = 4; - repeated string volumes = 5; + string id = 1; + string image = 2; + repeated Port ports = 3; + map labels = 4; + repeated string volumes = 5; } message RunResponse { } message ExecRequest { - string id = 1; - string command = 2; - string stream_id = 3; - repeated string args = 4; - repeated string env = 5; - bool tty = 6; + string id = 1; + string command = 2; + string stream_id = 3; + repeated string args = 4; + repeated string env = 5; + bool tty = 6; } message ExecResponse { - bytes output = 1; + bytes output = 1; } message ListRequest { - bool all = 1; + bool all = 1; } message ListResponse { - repeated Container containers = 1; + repeated Container containers = 1; } message LogsRequest { - string container_id = 1; - bool follow = 3; + string container_id = 1; + bool follow = 3; } message LogsResponse { - bytes value = 1; + bytes value = 1; } diff --git a/server/proxy/containers.go b/server/proxy/containers.go index ce5c7d44f..1ceb533d9 100644 --- a/server/proxy/containers.go +++ b/server/proxy/containers.go @@ -33,19 +33,7 @@ func (p *proxy) List(ctx context.Context, request *containersv1.ListRequest) (*c Containers: []*containersv1.Container{}, } for _, container := range containerList { - response.Containers = append(response.Containers, &containersv1.Container{ - Id: container.ID, - Image: container.Image, - Command: container.Command, - Status: container.Status, - CpuTime: container.CPUTime, - Labels: container.Labels, - MemoryLimit: container.MemoryLimit, - MemoryUsage: container.MemoryUsage, - PidsCurrent: container.PidsCurrent, - PidsLimit: container.PidsLimit, - Ports: portsToGrpc(container.Ports), - }) + response.Containers = append(response.Containers, toGrpcContainer(container)) } return response, nil @@ -82,19 +70,7 @@ func (p *proxy) Inspect(ctx context.Context, request *containersv1.InspectReques return nil, err } response := &containersv1.InspectResponse{ - Container: &containersv1.Container{ - Id: c.ID, - Image: c.Image, - Status: c.Status, - Command: c.Command, - CpuTime: c.CPUTime, - MemoryUsage: c.MemoryUsage, - MemoryLimit: c.MemoryLimit, - PidsCurrent: c.PidsCurrent, - PidsLimit: c.PidsLimit, - Labels: c.Labels, - Ports: portsToGrpc(c.Ports), - }, + Container: toGrpcContainer(c), } return response, err } @@ -126,3 +102,19 @@ func (p *proxy) Logs(request *containersv1.LogsRequest, stream containersv1.Cont }, }) } + +func toGrpcContainer(c containers.Container) *containersv1.Container { + return &containersv1.Container{ + Id: c.ID, + Image: c.Image, + Status: c.Status, + Command: c.Command, + CpuTime: c.CPUTime, + MemoryUsage: c.MemoryUsage, + MemoryLimit: c.MemoryLimit, + PidsCurrent: c.PidsCurrent, + PidsLimit: c.PidsLimit, + Labels: c.Labels, + Ports: portsToGrpc(c.Ports), + } +} diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 2344e37b8..8188115bb 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -186,6 +186,11 @@ func (s *E2eSuite) TestMockBackend() { Expect(lines[2]).To(Equal("stopped")) }) + It("can run inspect command on container", func() { + golden.Assert(s.T(), s.NewDockerCommand("inspect", "id").ExecOrDie(), + GoldenFile("inspect-id")) + }) + It("can run 'run' command", func() { output := s.NewDockerCommand("run", "nginx", "-p", "80:80").ExecOrDie() Expect(output).To(ContainSubstring("Running container \"nginx\" with name")) diff --git a/tests/e2e/testdata/inspect-id.golden b/tests/e2e/testdata/inspect-id.golden new file mode 100644 index 000000000..28473f4df --- /dev/null +++ b/tests/e2e/testdata/inspect-id.golden @@ -0,0 +1,13 @@ +{ + "ID": "id", + "Status": "", + "Image": "nginx", + "Command": "", + "CPUTime": 0, + "MemoryUsage": 0, + "MemoryLimit": 0, + "PidsCurrent": 0, + "PidsLimit": 0, + "Labels": null, + "Ports": null +}