diff --git a/internal/graphapi/generated.go b/internal/graphapi/generated.go index b59bde84..300608bf 100644 --- a/internal/graphapi/generated.go +++ b/internal/graphapi/generated.go @@ -187,6 +187,7 @@ type ComplexityRoot struct { Query struct { IPAddress func(childComplexity int, id gidx.PrefixedID) int + IPAddressByNode func(childComplexity int, id gidx.PrefixedID) int IPBlock func(childComplexity int, id gidx.PrefixedID) int IPBlockType func(childComplexity int, id gidx.PrefixedID) int __resolve__service func(childComplexity int) int @@ -232,6 +233,7 @@ type MutationResolver interface { } type QueryResolver interface { IPAddress(ctx context.Context, id gidx.PrefixedID) (*generated.IPAddress, error) + IPAddressByNode(ctx context.Context, id gidx.PrefixedID) (*generated.IPAddress, error) IPBlock(ctx context.Context, id gidx.PrefixedID) (*generated.IPBlock, error) IPBlockType(ctx context.Context, id gidx.PrefixedID) (*generated.IPBlockType, error) } @@ -801,6 +803,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.IPAddress(childComplexity, args["id"].(gidx.PrefixedID)), true + case "Query.ipAddressByNode": + if e.complexity.Query.IPAddressByNode == nil { + break + } + + args, err := ec.field_Query_ipAddressByNode_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.IPAddressByNode(childComplexity, args["id"].(gidx.PrefixedID)), true + case "Query.ipBlock": if e.complexity.Query.IPBlock == nil { break @@ -1434,6 +1448,15 @@ input UpdateIPBlockTypeInput { """ id: ID! ): IPAddress! + """ + Look up ip address by Node ID + """ + ipAddressByNode( + """ + ID of the Node + """ + id: ID! + ): IPAddress! } extend type Mutation{ @@ -2188,6 +2211,21 @@ func (ec *executionContext) field_Query__entities_args(ctx context.Context, rawA return args, nil } +func (ec *executionContext) field_Query_ipAddressByNode_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 gidx.PrefixedID + if tmp, ok := rawArgs["id"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + arg0, err = ec.unmarshalNID2goᚗinfratographerᚗcomᚋxᚋgidxᚐPrefixedID(ctx, tmp) + if err != nil { + return nil, err + } + } + args["id"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query_ipAddress_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -5778,6 +5816,77 @@ func (ec *executionContext) fieldContext_Query_ipAddress(ctx context.Context, fi return fc, nil } +func (ec *executionContext) _Query_ipAddressByNode(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_ipAddressByNode(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().IPAddressByNode(rctx, fc.Args["id"].(gidx.PrefixedID)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*generated.IPAddress) + fc.Result = res + return ec.marshalNIPAddress2ᚖgoᚗinfratographerᚗcomᚋipamᚑapiᚋinternalᚋentᚋgeneratedᚐIPAddress(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_ipAddressByNode(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_IPAddress_id(ctx, field) + case "createdAt": + return ec.fieldContext_IPAddress_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_IPAddress_updatedAt(ctx, field) + case "ip": + return ec.fieldContext_IPAddress_ip(ctx, field) + case "reserved": + return ec.fieldContext_IPAddress_reserved(ctx, field) + case "ipBlock": + return ec.fieldContext_IPAddress_ipBlock(ctx, field) + case "node": + return ec.fieldContext_IPAddress_node(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type IPAddress", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_ipAddressByNode_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Query_ipBlock(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_ipBlock(ctx, field) if err != nil { @@ -11257,6 +11366,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "ipAddressByNode": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_ipAddressByNode(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "ipBlock": field := field diff --git a/internal/graphapi/ipaddress.resolvers.go b/internal/graphapi/ipaddress.resolvers.go index 62f23fc1..1073c65b 100644 --- a/internal/graphapi/ipaddress.resolvers.go +++ b/internal/graphapi/ipaddress.resolvers.go @@ -83,6 +83,15 @@ func (r *queryResolver) IPAddress(ctx context.Context, id gidx.PrefixedID) (*gen return r.client.IPAddress.Get(ctx, id) } +// IPAddressByNode is the resolver for the ipAddressByNode field. +func (r *queryResolver) IPAddressByNode(ctx context.Context, id gidx.PrefixedID) (*generated.IPAddress, error) { + if err := permissions.CheckAccess(ctx, id, actionIPBlockGet); err != nil { + return nil, err + } + + return r.client.IPAddress.Query().Where(ipaddress.NodeID(id)).First(ctx) +} + // IPAddressable returns IPAddressableResolver implementation. func (r *Resolver) IPAddressable() IPAddressableResolver { return &iPAddressableResolver{r} } diff --git a/internal/graphapi/ipaddress_test.go b/internal/graphapi/ipaddress_test.go index 81faded0..0824ef4b 100644 --- a/internal/graphapi/ipaddress_test.go +++ b/internal/graphapi/ipaddress_test.go @@ -69,11 +69,12 @@ func Test_IPAddress_Lifecycle(t *testing.T) { ipbt := (&IPBlockTypeBuilder{}).MustNew(ctx) ipb := (&IPBlockBuilder{IPBlockTypeID: ipbt.ID}).MustNew(ctx) + node := gidx.MustNewID(nodePrefix) t.Run("Create", func(t *testing.T) { ipa, err := client.CreateIPAddress(ctx, testclient.CreateIPAddressInput{ IP: gofakeit.IPv4Address(), - NodeID: gidx.MustNewID(nodePrefix), + NodeID: node, NodeOwnerID: gidx.MustNewID(ownerPrefix), Reserved: newBool(true), IPBlockID: ipb.ID, @@ -90,6 +91,12 @@ func Test_IPAddress_Lifecycle(t *testing.T) { require.NotNil(t, ipaUpdate) require.Equal(t, false, ipaUpdate.UpdateIPAddress.IPAddress.Reserved) + addr, err := client.GetIPAddressByNode(ctx, node) + + require.NoError(t, err) + require.NotNil(t, addr) + require.Equal(t, addr.IPAddressByNode.ID, ipa.CreateIPAddress.IPAddress.ID) + ipaDelete, err := client.DeleteIPAddress(ctx, ipa.CreateIPAddress.IPAddress.ID) require.NoError(t, err) diff --git a/internal/testclient/gen_client.go b/internal/testclient/gen_client.go index 40a14e79..7ba14f6e 100644 --- a/internal/testclient/gen_client.go +++ b/internal/testclient/gen_client.go @@ -26,6 +26,7 @@ type TestClient interface { ListIPBlockTypes(ctx context.Context, id gidx.PrefixedID, orderBy *IPBlockTypeOrder, httpRequestOptions ...client.HTTPRequestOption) (*ListIPBlockTypes, error) UpdateIPAddress(ctx context.Context, id gidx.PrefixedID, input UpdateIPAddressInput, httpRequestOptions ...client.HTTPRequestOption) (*UpdateIPAddress, error) GetIPAddress(ctx context.Context, id gidx.PrefixedID, httpRequestOptions ...client.HTTPRequestOption) (*GetIPAddress, error) + GetIPAddressByNode(ctx context.Context, id gidx.PrefixedID, httpRequestOptions ...client.HTTPRequestOption) (*GetIPAddressByNode, error) } type Client struct { @@ -37,11 +38,12 @@ func NewClient(cli *http.Client, baseURL string, options ...client.HTTPRequestOp } type Query struct { - IPAddress IPAddress "json:\"ipAddress\" graphql:\"ipAddress\"" - IPBlock IPBlock "json:\"ipBlock\" graphql:\"ipBlock\"" - IPBlockType IPBlockType "json:\"ipBlockType\" graphql:\"ipBlockType\"" - Entities []Entity "json:\"_entities\" graphql:\"_entities\"" - Service Service "json:\"_service\" graphql:\"_service\"" + IPAddress IPAddress "json:\"ipAddress\" graphql:\"ipAddress\"" + IPAddressByNode IPAddress "json:\"ipAddressByNode\" graphql:\"ipAddressByNode\"" + IPBlock IPBlock "json:\"ipBlock\" graphql:\"ipBlock\"" + IPBlockType IPBlockType "json:\"ipBlockType\" graphql:\"ipBlockType\"" + Entities []Entity "json:\"_entities\" graphql:\"_entities\"" + Service Service "json:\"_service\" graphql:\"_service\"" } type Mutation struct { CreateIPAddress IPAddressCreatePayload "json:\"createIPAddress\" graphql:\"createIPAddress\"" @@ -239,6 +241,19 @@ type GetIPAddress struct { } "json:\"ipBlock\" graphql:\"ipBlock\"" } "json:\"ipAddress\" graphql:\"ipAddress\"" } +type GetIPAddressByNode struct { + IPAddressByNode struct { + ID gidx.PrefixedID "json:\"id\" graphql:\"id\"" + IP string "json:\"ip\" graphql:\"ip\"" + Reserved bool "json:\"reserved\" graphql:\"reserved\"" + IPBlock struct { + ID gidx.PrefixedID "json:\"id\" graphql:\"id\"" + Prefix string "json:\"prefix\" graphql:\"prefix\"" + AllowAutoAllocate bool "json:\"allowAutoAllocate\" graphql:\"allowAutoAllocate\"" + AllowAutoSubnet bool "json:\"allowAutoSubnet\" graphql:\"allowAutoSubnet\"" + } "json:\"ipBlock\" graphql:\"ipBlock\"" + } "json:\"ipAddressByNode\" graphql:\"ipAddressByNode\"" +} const CreateIPAddressDocument = `mutation CreateIPAddress ($input: CreateIPAddressInput!) { createIPAddress(input: $input) { @@ -642,3 +657,31 @@ func (c *Client) GetIPAddress(ctx context.Context, id gidx.PrefixedID, httpReque return &res, nil } + +const GetIPAddressByNodeDocument = `query getIPAddressByNode ($id: ID!) { + ipAddressByNode(id: $id) { + id + ip + reserved + ipBlock { + id + prefix + allowAutoAllocate + allowAutoSubnet + } + } +} +` + +func (c *Client) GetIPAddressByNode(ctx context.Context, id gidx.PrefixedID, httpRequestOptions ...client.HTTPRequestOption) (*GetIPAddressByNode, error) { + vars := map[string]interface{}{ + "id": id, + } + + var res GetIPAddressByNode + if err := c.Client.Post(ctx, "getIPAddressByNode", GetIPAddressByNodeDocument, &res, vars, httpRequestOptions...); err != nil { + return nil, err + } + + return &res, nil +} diff --git a/internal/testclient/ipaddress.gql b/internal/testclient/ipaddress.gql index 36e839dc..a87f0fd0 100644 --- a/internal/testclient/ipaddress.gql +++ b/internal/testclient/ipaddress.gql @@ -12,6 +12,20 @@ query getIPAddress($id: ID!) { } } +query getIPAddressByNode($id: ID!) { + ipAddressByNode(id: $id) { + id + ip + reserved + ipBlock { + id + prefix + allowAutoAllocate + allowAutoSubnet + } + } +} + mutation CreateIPAddress($input: CreateIPAddressInput!) { createIPAddress(input: $input) { ipAddress { diff --git a/internal/testclient/schema/schema.graphql b/internal/testclient/schema/schema.graphql index 3f3143f9..9252021b 100644 --- a/internal/testclient/schema/schema.graphql +++ b/internal/testclient/schema/schema.graphql @@ -539,6 +539,11 @@ type Query { """ID of the ip address""" id: ID! ): IPAddress! + """Look up ip address by Node ID""" + ipAddressByNode( + """ID of the Node""" + id: ID! + ): IPAddress! """Look up ip block by ID""" ipBlock( """ID of the ip block""" diff --git a/pkg/ipamclient/client.go b/pkg/ipamclient/client.go index 015d9151..2702f6a9 100644 --- a/pkg/ipamclient/client.go +++ b/pkg/ipamclient/client.go @@ -204,3 +204,22 @@ func (c *Client) GetIPAddresses(ctx context.Context, nodeID string) ([]IPAddress return nodeIPs.NodeIPAddress.LoadBalancerFragment.IPAddresses, nil } + +// GetIPAddressByNode returns an IP Address by node id +func (c *Client) GetIPAddressByNode(ctx context.Context, nodeID string) (*GetIPAddressByNode, error) { + _, err := gidx.Parse(nodeID) + if err != nil { + return nil, err + } + + vars := map[string]interface{}{ + "id": graphql.ID(nodeID), + } + + var nodeIP GetIPAddressByNode + if err := c.gqlCli.Query(ctx, &nodeIP, vars); err != nil { + return nil, err + } + + return &nodeIP, nil +} diff --git a/pkg/ipamclient/client_test.go b/pkg/ipamclient/client_test.go index c3607fc4..13e00ddc 100644 --- a/pkg/ipamclient/client_test.go +++ b/pkg/ipamclient/client_test.go @@ -236,6 +236,33 @@ func TestGetIPAddressesByNodeID(t *testing.T) { }) } +func TestGetIPAddressByNode(t *testing.T) { + cli := Client{ + gqlCli: mustNewGQLTestClient( + `{ + "data": { + "ipAddressByNode": { + "id": "ipamipa-12345", + "ip": "192.168.10.1", + "reserved": false + } + } + }`), + } + + ipResult, err := cli.GetIPAddressByNode(context.Background(), "nodeids-test") + require.NoError(t, err) + require.NotNil(t, ipResult) + + assert.Equal(t, ipResult.IPAddress.ID, "ipamipa-12345") + assert.Equal(t, ipResult.IPAddress.IP, "192.168.10.1") + assert.False(t, ipResult.IPAddress.Reserved) + + baddress, err := cli.GetIPAddressByNode(context.TODO(), "badprefix-test") + require.Error(t, err) + require.Nil(t, baddress) +} + func mustNewGQLTestClient(respJSON string) *graphql.Client { mux := http.NewServeMux() mux.HandleFunc("/query", func(w http.ResponseWriter, req *http.Request) { diff --git a/pkg/ipamclient/types.go b/pkg/ipamclient/types.go index 8ea0b87b..89aaa8bc 100644 --- a/pkg/ipamclient/types.go +++ b/pkg/ipamclient/types.go @@ -109,3 +109,8 @@ type GetIPAddressesByNode struct { } `graphql:"... on LoadBalancer"` } `graphql:"node(id: $id)"` } + +// GetIPAddressByNode is the query used for getting an IP Address +type GetIPAddressByNode struct { + IPAddress GetIPAddressResult `graphql:"ipAddressByNode(id: $id)"` +} diff --git a/schema.graphql b/schema.graphql index 732388bb..064104b2 100644 --- a/schema.graphql +++ b/schema.graphql @@ -526,6 +526,11 @@ type Query { """ID of the ip address""" id: ID! ): IPAddress! + """Look up ip address by Node ID""" + ipAddressByNode( + """ID of the Node""" + id: ID! + ): IPAddress! """Look up ip block by ID""" ipBlock( """ID of the ip block""" diff --git a/schema/ipaddress.graphql b/schema/ipaddress.graphql index 0ce61b19..ec23836f 100644 --- a/schema/ipaddress.graphql +++ b/schema/ipaddress.graphql @@ -8,6 +8,15 @@ extend type Query { """ id: ID! ): IPAddress! + """ + Look up ip address by Node ID + """ + ipAddressByNode( + """ + ID of the Node + """ + id: ID! + ): IPAddress! } extend type Mutation{