diff --git a/gql/schema.graphql b/gql/schema.graphql index d281e87ffa..de5f76efc2 100644 --- a/gql/schema.graphql +++ b/gql/schema.graphql @@ -1520,8 +1520,11 @@ scalar BigInt enum BillingStatus { CURRENT + DELINQUENT PAST_DUE SOURCE_REQUIRED + TRIAL_ACTIVE + TRIAL_ENDED } type Build implements Node { @@ -2804,7 +2807,7 @@ input CreateBuildInput { appName: ID! """ - Whether builder is remote or local + The kind of builder being used """ builderType: String! @@ -3452,6 +3455,11 @@ input CreateReleaseInput { """ appId: ID! + """ + The build ID linked to the release + """ + buildId: ID + """ A unique identifier for the client performing the mutation. """ @@ -3626,6 +3634,11 @@ input CreateUserSignupInput { Timestamp of when the user arrived at the website """ startedAt: String! + + """ + UTM query string params + """ + utmParams: JSON } """ @@ -4996,6 +5009,52 @@ type DummyWireGuardPeerPayload { pubkey: String! } +type EgressIPAddress implements Node { + """ + ID of the object. + """ + id: ID! + ip: String! + region: String! + version: Int! +} + +""" +The connection type for EgressIPAddress. +""" +type EgressIPAddressConnection { + """ + A list of edges. + """ + edges: [EgressIPAddressEdge] + + """ + A list of nodes. + """ + nodes: [EgressIPAddress] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EgressIPAddressEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EgressIPAddress +} + type EmptyAppRole implements AppRole { """ The name of this role @@ -5086,6 +5145,11 @@ input EnsureDepotRemoteBuilderInput { """ appName: String + """ + The scope of the builder; either "app" or "organization" + """ + builderScope: String + """ A unique identifier for the client performing the mutation. """ @@ -6306,6 +6370,27 @@ type Machine implements Node { app: App! config: JSON! createdAt: ISO8601DateTime! + egressIpAddresses( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): EgressIPAddressConnection! events( """ Returns the elements in the list that come after the specified cursor. diff --git a/internal/command/machine/egress_ip.go b/internal/command/machine/egress_ip.go index 2a0687e031..f424d41880 100644 --- a/internal/command/machine/egress_ip.go +++ b/internal/command/machine/egress_ip.go @@ -3,6 +3,7 @@ package machine import ( "context" "fmt" + "strings" "github.com/spf13/cobra" "github.com/superfly/flyctl/internal/appconfig" @@ -10,6 +11,8 @@ import ( "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/prompt" + "github.com/superfly/flyctl/internal/render" + "github.com/superfly/flyctl/iostreams" ) func newEgressIp() *cobra.Command { @@ -25,6 +28,7 @@ func newEgressIp() *cobra.Command { cmd.AddCommand( newAllocateEgressIp(), + newListEgressIps(), ) return cmd @@ -52,6 +56,27 @@ func newAllocateEgressIp() *cobra.Command { return cmd } +func newListEgressIps() *cobra.Command { + const ( + long = `List all allocated static egress IP addresses with their corresponding machine` + short = `List all allocated static egress IPs` + ) + + cmd := command.New("list", short, long, runListEgressIps, + command.RequireSession, + command.LoadAppNameIfPresent, + ) + + flag.Add(cmd, + flag.App(), + flag.AppConfig(), + ) + + cmd.Args = cobra.NoArgs + + return cmd +} + func runAllocateEgressIP(ctx context.Context) (err error) { var ( args = flag.Args(ctx) @@ -86,3 +111,29 @@ Are you sure this is what you want?` fmt.Printf("IPv6: %s\n", ipv6.String()) return nil } + +func runListEgressIps(ctx context.Context) (err error) { + var ( + client = flyutil.ClientFromContext(ctx) + appName = appconfig.NameFromContext(ctx) + ) + + machineIPs, err := client.GetEgressIPAddresses(ctx, appName) + if err != nil { + return err + } + + rows := make([][]string, 0, 1) + + for machine, ips := range machineIPs { + ipStr := make([]string, len(ips)) + for _, ip := range ips { + ipStr = append(ipStr, ip.String()) + } + rows = append(rows, []string{machine, strings.Join(ipStr, ",")}) + } + + out := iostreams.FromContext(ctx).Out + render.Table(out, "", rows, "Machine", "Egress IPs") + return nil +} diff --git a/internal/flyutil/client.go b/internal/flyutil/client.go index 841bacc89d..cf12c0fde9 100644 --- a/internal/flyutil/client.go +++ b/internal/flyutil/client.go @@ -69,6 +69,7 @@ type Client interface { GetDomain(ctx context.Context, name string) (*fly.Domain, error) GetDomains(ctx context.Context, organizationSlug string) ([]*fly.Domain, error) GetIPAddresses(ctx context.Context, appName string) ([]fly.IPAddress, error) + GetEgressIPAddresses(ctx context.Context, appName string) (map[string][]net.IP, error) GetLatestImageDetails(ctx context.Context, image string) (*fly.ImageVersion, error) GetLatestImageTag(ctx context.Context, repository string, snapshotId *string) (string, error) GetLoggedCertificates(ctx context.Context, slug string) ([]fly.LoggedCertificate, error) diff --git a/internal/inmem/client.go b/internal/inmem/client.go index 65cbd5785b..9c29adb5bf 100644 --- a/internal/inmem/client.go +++ b/internal/inmem/client.go @@ -292,6 +292,10 @@ func (m *Client) GetIPAddresses(ctx context.Context, appName string) ([]fly.IPAd return nil, nil // TODO } +func (c *Client) GetEgressIPAddresses(ctx context.Context, appName string) (map[string][]net.IP, error) { + panic("TODO") +} + func (m *Client) GetLatestImageDetails(ctx context.Context, image string) (*fly.ImageVersion, error) { panic("TODO") } diff --git a/internal/mock/client.go b/internal/mock/client.go index bb02ba4920..594578c99c 100644 --- a/internal/mock/client.go +++ b/internal/mock/client.go @@ -70,6 +70,7 @@ type Client struct { GetDomainFunc func(ctx context.Context, name string) (*fly.Domain, error) GetDomainsFunc func(ctx context.Context, organizationSlug string) ([]*fly.Domain, error) GetIPAddressesFunc func(ctx context.Context, appName string) ([]fly.IPAddress, error) + GetEgressIPAddressesFunc func(ctx context.Context, appName string) (map[string][]net.IP, error) GetLatestImageDetailsFunc func(ctx context.Context, image string) (*fly.ImageVersion, error) GetLatestImageTagFunc func(ctx context.Context, repository string, snapshotId *string) (string, error) GetLoggedCertificatesFunc func(ctx context.Context, slug string) ([]fly.LoggedCertificate, error) @@ -117,6 +118,10 @@ func (m *Client) AllocateSharedIPAddress(ctx context.Context, appName string) (n return m.AllocateSharedIPAddressFunc(ctx, appName) } +func (m *Client) AllocateEgressIPAddress(ctx context.Context, appName string, machineId string) (net.IP, net.IP, error) { + return m.AllocateEgressIPAddressFunc(ctx, appName, machineId) +} + func (m *Client) AppNameAvailable(ctx context.Context, appName string) (bool, error) { return m.AppNameAvailableFunc(ctx, appName) } @@ -325,6 +330,10 @@ func (m *Client) GetIPAddresses(ctx context.Context, appName string) ([]fly.IPAd return m.GetIPAddressesFunc(ctx, appName) } +func (m *Client) GetEgressIPAddresses(ctx context.Context, appName string) (map[string][]net.IP, error) { + return m.GetEgressIPAddressesFunc(ctx, appName) +} + func (m *Client) GetLatestImageDetails(ctx context.Context, image string) (*fly.ImageVersion, error) { return m.GetLatestImageDetailsFunc(ctx, image) }