From 9444f61a015c8cbcc8f30d513feb66430bf1cb57 Mon Sep 17 00:00:00 2001 From: Matthijs van Gastel Date: Tue, 3 Mar 2026 16:42:57 +0100 Subject: [PATCH 01/13] Added external connections for cloud databaseclusters --- api/generated.go | 256 ++++++++++++++++-- cmd/cloudDatabaseCluster.go | 3 + cmd/cloudDatabaseClusterExternalConnection.go | 92 +++++++ go.sum | 15 + operations/cloudDatabaseCluster.graphql | 3 + operations/generic.graphql | 11 + schema.graphql | 221 +++------------ 7 files changed, 404 insertions(+), 197 deletions(-) create mode 100644 cmd/cloudDatabaseClusterExternalConnection.go diff --git a/api/generated.go b/api/generated.go index c9150e1..5255122 100644 --- a/api/generated.go +++ b/api/generated.go @@ -56,12 +56,13 @@ var AllAutoScalingType = []AutoScalingType{ } type CloudDatabaseClusterCreateInput struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Spec CloudDatabaseClusterSpecInput `json:"spec"` - Plan string `json:"plan"` - Databases []DatabaseInput `json:"databases"` - Users []DatabaseUserInput `json:"users"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Spec CloudDatabaseClusterSpecInput `json:"spec"` + Plan string `json:"plan"` + Databases []DatabaseInput `json:"databases"` + Users []DatabaseUserInput `json:"users"` + ExternalConnection *ExternalConnectionInput `json:"externalConnection"` } // GetName returns CloudDatabaseClusterCreateInput.Name, and is useful for accessing the field via an interface. @@ -82,6 +83,11 @@ func (v *CloudDatabaseClusterCreateInput) GetDatabases() []DatabaseInput { retur // GetUsers returns CloudDatabaseClusterCreateInput.Users, and is useful for accessing the field via an interface. func (v *CloudDatabaseClusterCreateInput) GetUsers() []DatabaseUserInput { return v.Users } +// GetExternalConnection returns CloudDatabaseClusterCreateInput.ExternalConnection, and is useful for accessing the field via an interface. +func (v *CloudDatabaseClusterCreateInput) GetExternalConnection() *ExternalConnectionInput { + return v.ExternalConnection +} + type CloudDatabaseClusterDatabaseCreateInput struct { Cluster CloudDatabaseClusterResourceInput `json:"cluster"` Database DatabaseInput `json:"database"` @@ -125,10 +131,11 @@ func (v *CloudDatabaseClusterDatabaseResult) GetDescription() *string { return v func (v *CloudDatabaseClusterDatabaseResult) GetStatus() string { return v.Status } type CloudDatabaseClusterModifyInput struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Databases []DatabaseInput `json:"databases"` - Users []DatabaseUserInput `json:"users"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Databases []DatabaseInput `json:"databases"` + Users []DatabaseUserInput `json:"users"` + ExternalConnection *ExternalConnectionInput `json:"externalConnection"` } // GetName returns CloudDatabaseClusterModifyInput.Name, and is useful for accessing the field via an interface. @@ -143,6 +150,11 @@ func (v *CloudDatabaseClusterModifyInput) GetDatabases() []DatabaseInput { retur // GetUsers returns CloudDatabaseClusterModifyInput.Users, and is useful for accessing the field via an interface. func (v *CloudDatabaseClusterModifyInput) GetUsers() []DatabaseUserInput { return v.Users } +// GetExternalConnection returns CloudDatabaseClusterModifyInput.ExternalConnection, and is useful for accessing the field via an interface. +func (v *CloudDatabaseClusterModifyInput) GetExternalConnection() *ExternalConnectionInput { + return v.ExternalConnection +} + // CloudDatabaseClusterPlan includes the GraphQL fields of Plan requested by the fragment CloudDatabaseClusterPlan. type CloudDatabaseClusterPlan struct { Cpu int `json:"cpu"` @@ -200,17 +212,18 @@ func (v *CloudDatabaseClusterResourceInput) GetNamespace() string { return v.Nam // CloudDatabaseClusterResult includes the GraphQL fields of CloudDatabaseCluster requested by the fragment CloudDatabaseClusterResult. type CloudDatabaseClusterResult struct { - Id string `json:"id"` - Databases []CloudDatabaseClusterResultDatabasesDatabase `json:"databases"` - Name string `json:"name"` - Hostname string `json:"hostname"` - Namespace CloudDatabaseClusterResultNamespace `json:"namespace"` - Plan CloudDatabaseClusterResultPlan `json:"plan"` - Spec CloudDatabaseClusterResultSpec `json:"spec"` - Users []CloudDatabaseClusterResultUsersDatabaseUser `json:"users"` - AdminUser *CloudDatabaseClusterResultAdminUserDatabaseUser `json:"adminUser"` - State string `json:"state"` - Locked bool `json:"locked"` + Id string `json:"id"` + Databases []CloudDatabaseClusterResultDatabasesDatabase `json:"databases"` + Name string `json:"name"` + Hostname string `json:"hostname"` + Namespace CloudDatabaseClusterResultNamespace `json:"namespace"` + Plan CloudDatabaseClusterResultPlan `json:"plan"` + Spec CloudDatabaseClusterResultSpec `json:"spec"` + Users []CloudDatabaseClusterResultUsersDatabaseUser `json:"users"` + AdminUser *CloudDatabaseClusterResultAdminUserDatabaseUser `json:"adminUser"` + ExternalConnection *CloudDatabaseClusterResultExternalConnection `json:"externalConnection"` + State string `json:"state"` + Locked bool `json:"locked"` } // GetId returns CloudDatabaseClusterResult.Id, and is useful for accessing the field via an interface. @@ -248,6 +261,11 @@ func (v *CloudDatabaseClusterResult) GetAdminUser() *CloudDatabaseClusterResultA return v.AdminUser } +// GetExternalConnection returns CloudDatabaseClusterResult.ExternalConnection, and is useful for accessing the field via an interface. +func (v *CloudDatabaseClusterResult) GetExternalConnection() *CloudDatabaseClusterResultExternalConnection { + return v.ExternalConnection +} + // GetState returns CloudDatabaseClusterResult.State, and is useful for accessing the field via an interface. func (v *CloudDatabaseClusterResult) GetState() string { return v.State } @@ -418,6 +436,76 @@ func (v *CloudDatabaseClusterResultDatabasesDatabase) __premarshalJSON() (*__pre return &retval, nil } +// CloudDatabaseClusterResultExternalConnection includes the requested fields of the GraphQL type ExternalConnection. +type CloudDatabaseClusterResultExternalConnection struct { + ExternalConnectionResult `json:"-"` +} + +// GetIpv4 returns CloudDatabaseClusterResultExternalConnection.Ipv4, and is useful for accessing the field via an interface. +func (v *CloudDatabaseClusterResultExternalConnection) GetIpv4() string { + return v.ExternalConnectionResult.Ipv4 +} + +// GetIpv6 returns CloudDatabaseClusterResultExternalConnection.Ipv6, and is useful for accessing the field via an interface. +func (v *CloudDatabaseClusterResultExternalConnection) GetIpv6() string { + return v.ExternalConnectionResult.Ipv6 +} + +// GetPorts returns CloudDatabaseClusterResultExternalConnection.Ports, and is useful for accessing the field via an interface. +func (v *CloudDatabaseClusterResultExternalConnection) GetPorts() []ExternalConnectionResultPortsExternalConnectionPort { + return v.ExternalConnectionResult.Ports +} + +func (v *CloudDatabaseClusterResultExternalConnection) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *CloudDatabaseClusterResultExternalConnection + graphql.NoUnmarshalJSON + } + firstPass.CloudDatabaseClusterResultExternalConnection = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ExternalConnectionResult) + if err != nil { + return err + } + return nil +} + +type __premarshalCloudDatabaseClusterResultExternalConnection struct { + Ipv4 string `json:"ipv4"` + + Ipv6 string `json:"ipv6"` + + Ports []ExternalConnectionResultPortsExternalConnectionPort `json:"ports"` +} + +func (v *CloudDatabaseClusterResultExternalConnection) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *CloudDatabaseClusterResultExternalConnection) __premarshalJSON() (*__premarshalCloudDatabaseClusterResultExternalConnection, error) { + var retval __premarshalCloudDatabaseClusterResultExternalConnection + + retval.Ipv4 = v.ExternalConnectionResult.Ipv4 + retval.Ipv6 = v.ExternalConnectionResult.Ipv6 + retval.Ports = v.ExternalConnectionResult.Ports + return &retval, nil +} + // CloudDatabaseClusterResultNamespace includes the requested fields of the GraphQL type Namespace. type CloudDatabaseClusterResultNamespace struct { Name string `json:"name"` @@ -1628,6 +1716,80 @@ func (v *EnvironmentVariableResult) GetValue() *string { return v.Value } // GetSecret returns EnvironmentVariableResult.Secret, and is useful for accessing the field via an interface. func (v *EnvironmentVariableResult) GetSecret() bool { return v.Secret } +type ExternalConnectionInput struct { + SharedIp bool `json:"sharedIp"` + State State `json:"state"` + Ports []ExternalConnectionPortInput `json:"ports"` +} + +// GetSharedIp returns ExternalConnectionInput.SharedIp, and is useful for accessing the field via an interface. +func (v *ExternalConnectionInput) GetSharedIp() bool { return v.SharedIp } + +// GetState returns ExternalConnectionInput.State, and is useful for accessing the field via an interface. +func (v *ExternalConnectionInput) GetState() State { return v.State } + +// GetPorts returns ExternalConnectionInput.Ports, and is useful for accessing the field via an interface. +func (v *ExternalConnectionInput) GetPorts() []ExternalConnectionPortInput { return v.Ports } + +type ExternalConnectionPortInput struct { + ExternalPort *int `json:"externalPort"` + State State `json:"state"` + AllowList []AllowListInput `json:"allowList"` +} + +// GetExternalPort returns ExternalConnectionPortInput.ExternalPort, and is useful for accessing the field via an interface. +func (v *ExternalConnectionPortInput) GetExternalPort() *int { return v.ExternalPort } + +// GetState returns ExternalConnectionPortInput.State, and is useful for accessing the field via an interface. +func (v *ExternalConnectionPortInput) GetState() State { return v.State } + +// GetAllowList returns ExternalConnectionPortInput.AllowList, and is useful for accessing the field via an interface. +func (v *ExternalConnectionPortInput) GetAllowList() []AllowListInput { return v.AllowList } + +// ExternalConnectionResult includes the GraphQL fields of ExternalConnection requested by the fragment ExternalConnectionResult. +type ExternalConnectionResult struct { + Ipv4 string `json:"ipv4"` + Ipv6 string `json:"ipv6"` + Ports []ExternalConnectionResultPortsExternalConnectionPort `json:"ports"` +} + +// GetIpv4 returns ExternalConnectionResult.Ipv4, and is useful for accessing the field via an interface. +func (v *ExternalConnectionResult) GetIpv4() string { return v.Ipv4 } + +// GetIpv6 returns ExternalConnectionResult.Ipv6, and is useful for accessing the field via an interface. +func (v *ExternalConnectionResult) GetIpv6() string { return v.Ipv6 } + +// GetPorts returns ExternalConnectionResult.Ports, and is useful for accessing the field via an interface. +func (v *ExternalConnectionResult) GetPorts() []ExternalConnectionResultPortsExternalConnectionPort { + return v.Ports +} + +// ExternalConnectionResultPortsExternalConnectionPort includes the requested fields of the GraphQL type ExternalConnectionPort. +type ExternalConnectionResultPortsExternalConnectionPort struct { + AllowList []string `json:"allowList"` + ExternalPort int `json:"externalPort"` + InternalPort *int `json:"internalPort"` + Protocol string `json:"protocol"` +} + +// GetAllowList returns ExternalConnectionResultPortsExternalConnectionPort.AllowList, and is useful for accessing the field via an interface. +func (v *ExternalConnectionResultPortsExternalConnectionPort) GetAllowList() []string { + return v.AllowList +} + +// GetExternalPort returns ExternalConnectionResultPortsExternalConnectionPort.ExternalPort, and is useful for accessing the field via an interface. +func (v *ExternalConnectionResultPortsExternalConnectionPort) GetExternalPort() int { + return v.ExternalPort +} + +// GetInternalPort returns ExternalConnectionResultPortsExternalConnectionPort.InternalPort, and is useful for accessing the field via an interface. +func (v *ExternalConnectionResultPortsExternalConnectionPort) GetInternalPort() *int { + return v.InternalPort +} + +// GetProtocol returns ExternalConnectionResultPortsExternalConnectionPort.Protocol, and is useful for accessing the field via an interface. +func (v *ExternalConnectionResultPortsExternalConnectionPort) GetProtocol() string { return v.Protocol } + type HealthCheckInput struct { Port int `json:"port"` Path string `json:"path"` @@ -4195,6 +4357,9 @@ fragment CloudDatabaseClusterResult on CloudDatabaseCluster { adminUser { ... CloudDatabaseClusterUserResult } + externalConnection { + ... ExternalConnectionResult + } state locked } @@ -4214,6 +4379,16 @@ fragment CloudDatabaseClusterUserResult on DatabaseUser { password role } +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } +} ` func cloudDatabaseClusterCreate( @@ -4383,6 +4558,9 @@ fragment CloudDatabaseClusterResult on CloudDatabaseCluster { adminUser { ... CloudDatabaseClusterUserResult } + externalConnection { + ... ExternalConnectionResult + } state locked } @@ -4402,6 +4580,16 @@ fragment CloudDatabaseClusterUserResult on DatabaseUser { password role } +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } +} ` func cloudDatabaseClusterModify( @@ -5400,6 +5588,9 @@ fragment CloudDatabaseClusterResult on CloudDatabaseCluster { adminUser { ... CloudDatabaseClusterUserResult } + externalConnection { + ... ExternalConnectionResult + } state locked } @@ -5419,6 +5610,16 @@ fragment CloudDatabaseClusterUserResult on DatabaseUser { password role } +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } +} ` func getCloudDatabaseCluster( @@ -5609,6 +5810,9 @@ fragment CloudDatabaseClusterResult on CloudDatabaseCluster { adminUser { ... CloudDatabaseClusterUserResult } + externalConnection { + ... ExternalConnectionResult + } state locked } @@ -5628,6 +5832,16 @@ fragment CloudDatabaseClusterUserResult on DatabaseUser { password role } +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } +} ` func getCloudDatabaseClusters( diff --git a/cmd/cloudDatabaseCluster.go b/cmd/cloudDatabaseCluster.go index 8d03cd9..53d9db2 100644 --- a/cmd/cloudDatabaseCluster.go +++ b/cmd/cloudDatabaseCluster.go @@ -249,4 +249,7 @@ func init() { getClusterDatabaseUserCredentialsCmd.MarkFlagRequired("namespace") getClusterDatabaseUserCredentialsCmd.MarkFlagRequired("user") cloudDatabaseClusterCmd.AddCommand(getClusterDatabaseUserCredentialsCmd) + + //External Connection + cloudDatabaseClusterCmd.AddCommand(cloudDatabaseClusterEnableExternalConnectionCmd) } diff --git a/cmd/cloudDatabaseClusterExternalConnection.go b/cmd/cloudDatabaseClusterExternalConnection.go new file mode 100644 index 0000000..e943530 --- /dev/null +++ b/cmd/cloudDatabaseClusterExternalConnection.go @@ -0,0 +1,92 @@ +package cmd + +import ( + "fmt" + "log" + + "github.com/nexaa-cloud/nexaa-cli/api" + "github.com/spf13/cobra" +) + +var cloudDatabaseClusterEnableExternalConnectionCmd = &cobra.Command{ + Use: "external-connection", + Short: "Enable or Disable external connection on a cloud database cluster", +} + +var enableCloudDatabaseClusterExternalConnectionCmd = &cobra.Command{ + Use: "enable", + Short: "Enable external connection on a cloud database cluster", + Run: func(cmd *cobra.Command, args []string) { + namespace, _ := cmd.Flags().GetString("namespace") + clusterName, _ := cmd.Flags().GetString("cluster") + allowedIp, _ := cmd.Flags().GetStringArray("allowed-ip") + + allowList := make([]api.AllowListInput, 0) + for _, ip := range(allowedIp) { + allowList = append(allowList, api.AllowListInput{Ip:ip}) + } + + resource := api.CloudDatabaseClusterModifyInput{ + Name: clusterName, + Namespace: namespace, + ExternalConnection: &api.ExternalConnectionInput { + SharedIp: true, + Ports: []api.ExternalConnectionPortInput { + api.ExternalConnectionPortInput{AllowList: allowList}, + } , + }, + } + + client := api.NewClient() + + cluster, err := client.CloudDatabaseClusterModify(resource) + if err != nil { + log.Fatalf("Failed to enable external connection in cluster %q/%q: %v", namespace, clusterName, err) + return + } + fmt.Printf("External connection enabled. Reachable at:\n") + fmt.Printf("Ipv4: %q:%q \n", cluster.ExternalConnection.Ipv4, cluster.ExternalConnection.Ports[0].ExternalPort) + fmt.Printf("Ipv6: %q:%q \n", cluster.ExternalConnection.Ipv6, cluster.ExternalConnection.Ports[0].ExternalPort) + }, +} + +var disableCloudDatabaseClusterExternalConnectionCmd = &cobra.Command{ + Use: "disable", + Short: "disable external connection on a cloud database cluster", + Run: func(cmd *cobra.Command, args []string) { + namespace, _ := cmd.Flags().GetString("namespace") + clusterName, _ := cmd.Flags().GetString("cluster") + + resource := api.CloudDatabaseClusterModifyInput{ + Name: clusterName, + Namespace: namespace, + ExternalConnection: &api.ExternalConnectionInput { + State: api.StateAbsent, + }, + } + + client := api.NewClient() + + cluster, err := client.CloudDatabaseClusterModify(resource) + if err != nil { + log.Fatalf("Failed to disable external connection in cluster %q/%q: %v", namespace, clusterName, err) + return + } + fmt.Printf("External connection disabled in: %q/%q. \n", cluster.Namespace.Name, cluster.Name) + }, +} + +func init() { + enableCloudDatabaseClusterExternalConnectionCmd.Flags().String("namespace", "", "Namespace") + enableCloudDatabaseClusterExternalConnectionCmd.Flags().String("cluster", "", "Name of the cluster") + enableCloudDatabaseClusterExternalConnectionCmd.Flags().StringArray("allowed-ip", []string{"0.0.0.0/0", "::/0"}, "Allowed ip for the connection") + enableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("namespace") + enableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("cluster") + cloudDatabaseClusterEnableExternalConnectionCmd.AddCommand(enableCloudDatabaseClusterExternalConnectionCmd) + + disableCloudDatabaseClusterExternalConnectionCmd.Flags().String("namespace", "", "Namespace") + disableCloudDatabaseClusterExternalConnectionCmd.Flags().String("cluster", "", "Name of the cluster") + disableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("namespace") + disableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("cluster") + cloudDatabaseClusterEnableExternalConnectionCmd.AddCommand(disableCloudDatabaseClusterExternalConnectionCmd) +} diff --git a/go.sum b/go.sum index e79d515..ec00107 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,15 @@ github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= +github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= +github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= +github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -42,15 +50,22 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k= github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/operations/cloudDatabaseCluster.graphql b/operations/cloudDatabaseCluster.graphql index cde78ce..94d9f65 100644 --- a/operations/cloudDatabaseCluster.graphql +++ b/operations/cloudDatabaseCluster.graphql @@ -30,6 +30,9 @@ fragment CloudDatabaseClusterResult on CloudDatabaseCluster { adminUser { ...CloudDatabaseClusterUserResult } + externalConnection { + ...ExternalConnectionResult + } state locked } diff --git a/operations/generic.graphql b/operations/generic.graphql index e13e9f4..5d83ba6 100644 --- a/operations/generic.graphql +++ b/operations/generic.graphql @@ -10,4 +10,15 @@ fragment ContainerMounts on Mount { name size } +} + +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } } \ No newline at end of file diff --git a/schema.graphql b/schema.graphql index 5dd658f..fb890de 100644 --- a/schema.graphql +++ b/schema.graphql @@ -91,6 +91,7 @@ type CloudDatabase { type CloudDatabaseCluster { adminUser: DatabaseUser databases: [Database!]! + externalConnection: ExternalConnection hostname: String! id: String! locked: Boolean! @@ -121,6 +122,9 @@ input CloudDatabaseClusterCreateInput { "" users: [DatabaseUserInput!] = null + + "" + externalConnection: ExternalConnectionInput = null } input CloudDatabaseClusterDatabaseCreateInput { @@ -151,6 +155,9 @@ input CloudDatabaseClusterModifyInput { "" users: [DatabaseUserInput!] + + "" + externalConnection: ExternalConnectionInput } input CloudDatabaseClusterResourceInput { @@ -193,54 +200,6 @@ input CloudDatabaseClusterUserResourceInput { name: String! } -input ConfigureContainerInput { - "" - containerId: Int! - - "" - resourceSpecificationId: Int = null - - "" - image: String = null - - "" - numberOfContainers: Int = null - - "" - environmentVariables: EnvironmentVariableModificationInput = null - - "" - ingresses: IngressModificationInput = null - - "" - unmount: [UnmountInput!] = null - - "" - ports: [String!] - - "" - privateRegistryId: Int = null - - "" - mounts: [MountInput!] - - """ - Health check performed to check the status of the container. - Health checks are directly performed on the container so your application must expose - the port. Port mappings specified in the ports field are not used by the health check. - - When you want to disable the health check you must send us a null value. To leave the - current health check unchanged you can omit this field. - """ - healthCheck: HealthCheckInput = null - - "" - autoScalingInput: AutoScalingInput = null - - "" - scaling: ScalingInput = null -} - type Container { autoScaling: AutoScaling availableReplicas: Int! @@ -720,54 +679,6 @@ enum CpuUnit { CPU } -input CreateContainerInput { - "" - namespaceId: Int! - - "" - resourceSpecificationId: Int! - - "" - name: String! - - "" - image: String! - - "" - replicas: Int = 1 - - "" - environmentVariables: [EnvironmentVariableInput!] = [] - - "" - ingresses: [IngressInput!] = [] - - "" - ports: [String!] - - "" - privateRegistryId: Int = null - - "" - mounts: [MountInput!] - - """ - Health check performed to check the status of the container. - Health checks are directly performed on the container so your application must expose - the port. Port mappings specified in the ports field are not used by the health check. - - When you want to disable the health check you must send us a null value. To leave the - current health check unchanged you can omit this field. - """ - healthCheck: HealthCheckInput = null - - "" - autoScalingInput: AutoScalingInput = null - - "" - scaling: ScalingInput = null -} - type Customer { address: Address @deprecated(reason: "this field will be removed in the future") billingPeriod: Int! @deprecated(reason: "this field will be removed in the future") @@ -781,6 +692,7 @@ type Customer { type Database { description: String + extensions: [Extension!]! name: String! status: String! } @@ -864,7 +776,7 @@ input DeleteRegistryConnectionInput { } type EnvironmentVariable { - id: ID! + id: ID! @deprecated(reason: "environment variables are identified by name") name: String! secret: Boolean! value: String @@ -884,15 +796,43 @@ input EnvironmentVariableInput { state: State! = PRESENT } -input EnvironmentVariableModificationInput { +type Extension { + name: String! +} + +type ExternalConnection { + ipv4: String! + ipv6: String! + ports: [ExternalConnectionPort!]! +} + +input ExternalConnectionInput { "" - remove: [RemoveEnvironmentVariableInput!] = null + sharedIp: Boolean! = true "" - update: [UpdateEnvironmentVariableInput!] = null + state: State! = PRESENT "" - add: [EnvironmentVariableInput!] = null + ports: [ExternalConnectionPortInput!]! = [] +} + +type ExternalConnectionPort { + allowList: [String!]! + externalPort: Int! + internalPort: Int + protocol: String! +} + +input ExternalConnectionPortInput { + "" + externalPort: Int = null + + "" + state: State! = PRESENT + + "" + allowList: [AllowListInput!]! = [] } type HealthCheck { @@ -934,17 +874,6 @@ input IngressInput { state: State! = PRESENT } -input IngressModificationInput { - "" - remove: [Int!] = null - - "" - update: [UpdateIngressInput!] = null - - "" - add: [IngressInput!] = null -} - type Location { id: Int! name: String! @@ -962,6 +891,8 @@ type MessageQueue { locked: Boolean! name: String! namespace: Namespace! + plan: MessageQueuePlan! + spec: MessageQueueSpec! state: String! } @@ -1164,18 +1095,6 @@ type Mutation { """ - Cost: complexity = 100, multipliers = [], defaultMultiplier = null - """ - createCloudDatabase(customerId: Int!, name: String!, username: String!, version: String!, nodeType: String!, password: String!, location: String!, whitelist: [String!]! = []): Boolean! @deprecated(reason: "Use `createCloudDatabaseCluster` mutation instead") - - """ - - Cost: complexity = 100, multipliers = [], defaultMultiplier = null - """ - createContainer(containerInput: CreateContainerInput!): Boolean! @deprecated(reason: "use `containerCreate` mutation instead") - - """ - Cost: complexity = 100, multipliers = [], defaultMultiplier = null """ createNamespace(name: String!, customerId: Int = null, description: String = null, pricingPlanId: Int = 2, resourceSpecificationId: Int = null): Boolean! @deprecated(reason: "use `namespaceCreate` mutation") @@ -1188,12 +1107,6 @@ type Mutation { """ - Cost: complexity = 100, multipliers = [], defaultMultiplier = null - """ - deleteContainer(containerId: Int!): Boolean! @deprecated(reason: "use `containerDelete` mutation instead") - - """ - Cost: complexity = 100, multipliers = [], defaultMultiplier = null """ deleteNamespace(id: Int!): Boolean! @deprecated(reason: "use `namespaceDelete` mutation") @@ -1237,7 +1150,7 @@ type Mutation { Cost: complexity = 100, multipliers = [], defaultMultiplier = null """ - messageQueueModify(messageQueue: CloudDatabaseClusterModifyInput): MessageQueue! + messageQueueModify(messageQueue: MessageQueueCreateInput!): MessageQueue! """ @@ -1247,12 +1160,6 @@ type Mutation { """ - Cost: complexity = 100, multipliers = [], defaultMultiplier = null - """ - modifyContainer(containerInput: ConfigureContainerInput!): Boolean! @deprecated(reason: "use `containerModify` mutation instead") - - """ - Cost: complexity = 100, multipliers = [], defaultMultiplier = null """ namespaceCreate(namespaceInput: NamespaceCreateInput!): Namespace! @@ -1558,11 +1465,6 @@ input RegistryCreateInput { verify: Boolean! = true } -input RemoveEnvironmentVariableInput { - "" - name: String! -} - type Replica { name: String! status: String! @@ -1621,7 +1523,7 @@ enum Status { } type Subscription { - "A placeholder query used by thecodingmachine\/graphqlite when there are no declared subscriptions." + "A placeholder query used by thecodingmachine/graphqlite when there are no declared subscriptions." dummySubscription: String } @@ -1639,39 +1541,6 @@ enum Unit { TB } -input UnmountInput { - "Name of the volume to unmount" - volume: String! - - "Path to unmount the volume in the container." - path: String! -} - -input UpdateEnvironmentVariableInput { - "" - name: String! - - "" - value: String! -} - -input UpdateIngressInput { - "" - id: ID! - - "" - domainName: String! - - "" - port: Int! - - "" - enableTLS: Boolean! - - "" - whitelist: [String!] = null -} - type Volume { containerJobs: [ContainerJob!]! containers: [Container!]! From 0faac1482db54760af951957eb6122ee8af18e1b Mon Sep 17 00:00:00 2001 From: Stijn Mommersteeg Date: Tue, 3 Mar 2026 17:14:04 +0100 Subject: [PATCH 02/13] Fixed missing properties --- cmd/cloudDatabaseClusterExternalConnection.go | 37 +++++++++++-------- schema.graphql | 2 +- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/cmd/cloudDatabaseClusterExternalConnection.go b/cmd/cloudDatabaseClusterExternalConnection.go index e943530..cc46044 100644 --- a/cmd/cloudDatabaseClusterExternalConnection.go +++ b/cmd/cloudDatabaseClusterExternalConnection.go @@ -9,8 +9,8 @@ import ( ) var cloudDatabaseClusterEnableExternalConnectionCmd = &cobra.Command{ - Use: "external-connection", - Short: "Enable or Disable external connection on a cloud database cluster", + Use: "external-connection", + Short: "Enable or Disable external connection on a cloud database cluster", } var enableCloudDatabaseClusterExternalConnectionCmd = &cobra.Command{ @@ -20,20 +20,24 @@ var enableCloudDatabaseClusterExternalConnectionCmd = &cobra.Command{ namespace, _ := cmd.Flags().GetString("namespace") clusterName, _ := cmd.Flags().GetString("cluster") allowedIp, _ := cmd.Flags().GetStringArray("allowed-ip") - + allowList := make([]api.AllowListInput, 0) - for _, ip := range(allowedIp) { - allowList = append(allowList, api.AllowListInput{Ip:ip}) + for _, ip := range allowedIp { + allowList = append(allowList, api.AllowListInput{Ip: ip, State: api.StatePresent}) } resource := api.CloudDatabaseClusterModifyInput{ Name: clusterName, Namespace: namespace, - ExternalConnection: &api.ExternalConnectionInput { + ExternalConnection: &api.ExternalConnectionInput{ + State: api.StatePresent, SharedIp: true, - Ports: []api.ExternalConnectionPortInput { - api.ExternalConnectionPortInput{AllowList: allowList}, - } , + Ports: []api.ExternalConnectionPortInput{ + { + AllowList: allowList, + State: api.StatePresent, + }, + }, }, } @@ -45,8 +49,8 @@ var enableCloudDatabaseClusterExternalConnectionCmd = &cobra.Command{ return } fmt.Printf("External connection enabled. Reachable at:\n") - fmt.Printf("Ipv4: %q:%q \n", cluster.ExternalConnection.Ipv4, cluster.ExternalConnection.Ports[0].ExternalPort) - fmt.Printf("Ipv6: %q:%q \n", cluster.ExternalConnection.Ipv6, cluster.ExternalConnection.Ports[0].ExternalPort) + fmt.Printf("Ipv4: %s:%d \n", cluster.ExternalConnection.Ipv4, cluster.ExternalConnection.Ports[0].ExternalPort) + fmt.Printf("Ipv6: %s:%d \n", cluster.ExternalConnection.Ipv6, cluster.ExternalConnection.Ports[0].ExternalPort) }, } @@ -56,12 +60,13 @@ var disableCloudDatabaseClusterExternalConnectionCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { namespace, _ := cmd.Flags().GetString("namespace") clusterName, _ := cmd.Flags().GetString("cluster") - + resource := api.CloudDatabaseClusterModifyInput{ Name: clusterName, Namespace: namespace, - ExternalConnection: &api.ExternalConnectionInput { + ExternalConnection: &api.ExternalConnectionInput{ State: api.StateAbsent, + Ports: []api.ExternalConnectionPortInput{}, }, } @@ -72,7 +77,7 @@ var disableCloudDatabaseClusterExternalConnectionCmd = &cobra.Command{ log.Fatalf("Failed to disable external connection in cluster %q/%q: %v", namespace, clusterName, err) return } - fmt.Printf("External connection disabled in: %q/%q. \n", cluster.Namespace.Name, cluster.Name) + fmt.Printf("External connection disabled in: %s/%s. \n", cluster.Namespace.Name, cluster.Name) }, } @@ -87,6 +92,6 @@ func init() { disableCloudDatabaseClusterExternalConnectionCmd.Flags().String("namespace", "", "Namespace") disableCloudDatabaseClusterExternalConnectionCmd.Flags().String("cluster", "", "Name of the cluster") disableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("namespace") - disableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("cluster") - cloudDatabaseClusterEnableExternalConnectionCmd.AddCommand(disableCloudDatabaseClusterExternalConnectionCmd) + disableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("cluster") + cloudDatabaseClusterEnableExternalConnectionCmd.AddCommand(disableCloudDatabaseClusterExternalConnectionCmd) } diff --git a/schema.graphql b/schema.graphql index fb890de..fbbbd32 100644 --- a/schema.graphql +++ b/schema.graphql @@ -814,7 +814,7 @@ input ExternalConnectionInput { state: State! = PRESENT "" - ports: [ExternalConnectionPortInput!]! = [] + ports: [ExternalConnectionPortInput!] = [] } type ExternalConnectionPort { From eb4b24de67d90158d0507fc8cd893c88ebb53700 Mon Sep 17 00:00:00 2001 From: Matthijs van Gastel Date: Wed, 4 Mar 2026 10:16:09 +0100 Subject: [PATCH 03/13] Updated go.mod dependencies --- go.sum | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/go.sum b/go.sum index ec00107..e79d515 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,7 @@ github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= -github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= -github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= -github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= -github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= -github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= -github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= -github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -50,22 +42,15 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k= github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 7e965f392d7af106bb03371389fcd12ca7576d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A8lios=20Stigter?= Date: Thu, 5 Mar 2026 16:42:28 +0100 Subject: [PATCH 04/13] Added external connections for message queues --- api/generated.go | 286 ++++++++++++++++++++++++++++++-- api/messageQueue.go | 8 + cmd/messageQueue.go | 2 + go.sum | 15 ++ main.go | 3 + operations/MessageQueue.graphql | 11 ++ schema.graphql | 130 ++++++++++++++- 7 files changed, 435 insertions(+), 20 deletions(-) diff --git a/api/generated.go b/api/generated.go index 5255122..eb2ac0b 100644 --- a/api/generated.go +++ b/api/generated.go @@ -55,6 +55,24 @@ var AllAutoScalingType = []AutoScalingType{ AutoScalingTypeCpu, } +// Enable this feature to get recommendations on how to improve your database usage. +// +// The tool will be providing you settings on a cluster level to tune your database, provide recommendations for +// missing indexes and more. +// +// Database clusters will be analyzed once a day. +type CloudDatabaseAdvisorInput struct { + // Set this field to `true` to enable recommendations on how to improve your database usage. + Enabled bool `json:"enabled"` +} + +// GetEnabled returns CloudDatabaseAdvisorInput.Enabled, and is useful for accessing the field via an interface. +func (v *CloudDatabaseAdvisorInput) GetEnabled() bool { return v.Enabled } + +// Input for create cloud database cluster. +// +// A database cluster is accessible from within the namespace your created it in. +// There is no limit on the number of users, or database. type CloudDatabaseClusterCreateInput struct { Name string `json:"name"` Namespace string `json:"namespace"` @@ -62,6 +80,7 @@ type CloudDatabaseClusterCreateInput struct { Plan string `json:"plan"` Databases []DatabaseInput `json:"databases"` Users []DatabaseUserInput `json:"users"` + Advisor *CloudDatabaseAdvisorInput `json:"advisor"` ExternalConnection *ExternalConnectionInput `json:"externalConnection"` } @@ -83,6 +102,9 @@ func (v *CloudDatabaseClusterCreateInput) GetDatabases() []DatabaseInput { retur // GetUsers returns CloudDatabaseClusterCreateInput.Users, and is useful for accessing the field via an interface. func (v *CloudDatabaseClusterCreateInput) GetUsers() []DatabaseUserInput { return v.Users } +// GetAdvisor returns CloudDatabaseClusterCreateInput.Advisor, and is useful for accessing the field via an interface. +func (v *CloudDatabaseClusterCreateInput) GetAdvisor() *CloudDatabaseAdvisorInput { return v.Advisor } + // GetExternalConnection returns CloudDatabaseClusterCreateInput.ExternalConnection, and is useful for accessing the field via an interface. func (v *CloudDatabaseClusterCreateInput) GetExternalConnection() *ExternalConnectionInput { return v.ExternalConnection @@ -130,12 +152,17 @@ func (v *CloudDatabaseClusterDatabaseResult) GetDescription() *string { return v // GetStatus returns CloudDatabaseClusterDatabaseResult.Status, and is useful for accessing the field via an interface. func (v *CloudDatabaseClusterDatabaseResult) GetStatus() string { return v.Status } +// Input for create cloud database cluster. +// +// A database cluster is accessible from within the namespace your created it in. +// There is no limit on the number of users, or database. type CloudDatabaseClusterModifyInput struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Databases []DatabaseInput `json:"databases"` - Users []DatabaseUserInput `json:"users"` - ExternalConnection *ExternalConnectionInput `json:"externalConnection"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Databases []DatabaseInput `json:"databases"` + Users []DatabaseUserInput `json:"users"` + Advisor *CloudDatabaseAdvisorInput `json:"advisor"` + ExternalConnection *ExternalConnectionInput `json:"externalConnection"` } // GetName returns CloudDatabaseClusterModifyInput.Name, and is useful for accessing the field via an interface. @@ -150,6 +177,9 @@ func (v *CloudDatabaseClusterModifyInput) GetDatabases() []DatabaseInput { retur // GetUsers returns CloudDatabaseClusterModifyInput.Users, and is useful for accessing the field via an interface. func (v *CloudDatabaseClusterModifyInput) GetUsers() []DatabaseUserInput { return v.Users } +// GetAdvisor returns CloudDatabaseClusterModifyInput.Advisor, and is useful for accessing the field via an interface. +func (v *CloudDatabaseClusterModifyInput) GetAdvisor() *CloudDatabaseAdvisorInput { return v.Advisor } + // GetExternalConnection returns CloudDatabaseClusterModifyInput.ExternalConnection, and is useful for accessing the field via an interface. func (v *CloudDatabaseClusterModifyInput) GetExternalConnection() *ExternalConnectionInput { return v.ExternalConnection @@ -1832,11 +1862,12 @@ type ManualScalingInput struct { func (v *ManualScalingInput) GetReplicas() int { return v.Replicas } type MessageQueueCreateInput struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Plan string `json:"plan"` - Spec MessageQueueSpecInput `json:"spec"` - AllowList []AllowListInput `json:"allowList"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Plan string `json:"plan"` + Spec MessageQueueSpecInput `json:"spec"` + AllowList []AllowListInput `json:"allowList"` + ExternalConnection *ExternalConnectionInput `json:"externalConnection"` } // GetName returns MessageQueueCreateInput.Name, and is useful for accessing the field via an interface. @@ -1854,6 +1885,32 @@ func (v *MessageQueueCreateInput) GetSpec() MessageQueueSpecInput { return v.Spe // GetAllowList returns MessageQueueCreateInput.AllowList, and is useful for accessing the field via an interface. func (v *MessageQueueCreateInput) GetAllowList() []AllowListInput { return v.AllowList } +// GetExternalConnection returns MessageQueueCreateInput.ExternalConnection, and is useful for accessing the field via an interface. +func (v *MessageQueueCreateInput) GetExternalConnection() *ExternalConnectionInput { + return v.ExternalConnection +} + +type MessageQueueModifyInput struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + AllowList []AllowListInput `json:"allowList"` + ExternalConnection *ExternalConnectionInput `json:"externalConnection"` +} + +// GetName returns MessageQueueModifyInput.Name, and is useful for accessing the field via an interface. +func (v *MessageQueueModifyInput) GetName() string { return v.Name } + +// GetNamespace returns MessageQueueModifyInput.Namespace, and is useful for accessing the field via an interface. +func (v *MessageQueueModifyInput) GetNamespace() string { return v.Namespace } + +// GetAllowList returns MessageQueueModifyInput.AllowList, and is useful for accessing the field via an interface. +func (v *MessageQueueModifyInput) GetAllowList() []AllowListInput { return v.AllowList } + +// GetExternalConnection returns MessageQueueModifyInput.ExternalConnection, and is useful for accessing the field via an interface. +func (v *MessageQueueModifyInput) GetExternalConnection() *ExternalConnectionInput { + return v.ExternalConnection +} + // MessageQueuePlanResult includes the GraphQL fields of MessageQueuePlan requested by the fragment MessageQueuePlanResult. type MessageQueuePlanResult struct { Cpu float64 `json:"cpu"` @@ -1915,12 +1972,13 @@ func (v *MessageQueueResourceInput) GetNamespace() string { return v.Namespace } // MessageQueueResult includes the GraphQL fields of MessageQueue requested by the fragment MessageQueueResult. type MessageQueueResult struct { - Id string `json:"id"` - Locked bool `json:"locked"` - Name string `json:"name"` - State string `json:"state"` - Namespace MessageQueueResultNamespace `json:"namespace"` - AdminUser *MessageQueueResultAdminUserMessageQueueUser `json:"adminUser"` + Id string `json:"id"` + Locked bool `json:"locked"` + Name string `json:"name"` + State string `json:"state"` + Namespace MessageQueueResultNamespace `json:"namespace"` + AdminUser *MessageQueueResultAdminUserMessageQueueUser `json:"adminUser"` + ExternalConnection *MessageQueueResultExternalConnection `json:"externalConnection"` } // GetId returns MessageQueueResult.Id, and is useful for accessing the field via an interface. @@ -1943,6 +2001,11 @@ func (v *MessageQueueResult) GetAdminUser() *MessageQueueResultAdminUserMessageQ return v.AdminUser } +// GetExternalConnection returns MessageQueueResult.ExternalConnection, and is useful for accessing the field via an interface. +func (v *MessageQueueResult) GetExternalConnection() *MessageQueueResultExternalConnection { + return v.ExternalConnection +} + // MessageQueueResultAdminUserMessageQueueUser includes the requested fields of the GraphQL type MessageQueueUser. type MessageQueueResultAdminUserMessageQueueUser struct { Name string `json:"name"` @@ -1959,6 +2022,76 @@ func (v *MessageQueueResultAdminUserMessageQueueUser) GetRole() string { return // GetStatus returns MessageQueueResultAdminUserMessageQueueUser.Status, and is useful for accessing the field via an interface. func (v *MessageQueueResultAdminUserMessageQueueUser) GetStatus() string { return v.Status } +// MessageQueueResultExternalConnection includes the requested fields of the GraphQL type ExternalConnection. +type MessageQueueResultExternalConnection struct { + ExternalConnectionResult `json:"-"` +} + +// GetIpv4 returns MessageQueueResultExternalConnection.Ipv4, and is useful for accessing the field via an interface. +func (v *MessageQueueResultExternalConnection) GetIpv4() string { + return v.ExternalConnectionResult.Ipv4 +} + +// GetIpv6 returns MessageQueueResultExternalConnection.Ipv6, and is useful for accessing the field via an interface. +func (v *MessageQueueResultExternalConnection) GetIpv6() string { + return v.ExternalConnectionResult.Ipv6 +} + +// GetPorts returns MessageQueueResultExternalConnection.Ports, and is useful for accessing the field via an interface. +func (v *MessageQueueResultExternalConnection) GetPorts() []ExternalConnectionResultPortsExternalConnectionPort { + return v.ExternalConnectionResult.Ports +} + +func (v *MessageQueueResultExternalConnection) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *MessageQueueResultExternalConnection + graphql.NoUnmarshalJSON + } + firstPass.MessageQueueResultExternalConnection = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ExternalConnectionResult) + if err != nil { + return err + } + return nil +} + +type __premarshalMessageQueueResultExternalConnection struct { + Ipv4 string `json:"ipv4"` + + Ipv6 string `json:"ipv6"` + + Ports []ExternalConnectionResultPortsExternalConnectionPort `json:"ports"` +} + +func (v *MessageQueueResultExternalConnection) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *MessageQueueResultExternalConnection) __premarshalJSON() (*__premarshalMessageQueueResultExternalConnection, error) { + var retval __premarshalMessageQueueResultExternalConnection + + retval.Ipv4 = v.ExternalConnectionResult.Ipv4 + retval.Ipv6 = v.ExternalConnectionResult.Ipv6 + retval.Ports = v.ExternalConnectionResult.Ports + return &retval, nil +} + // MessageQueueResultNamespace includes the requested fields of the GraphQL type Namespace. type MessageQueueResultNamespace struct { Name string `json:"name"` @@ -2563,6 +2696,16 @@ func (v *__messageQueueGetInput) GetMessageQueueInput() MessageQueueResourceInpu return v.MessageQueueInput } +// __messageQueueModifyInput is used internally by genqlient +type __messageQueueModifyInput struct { + MessageQueueInput MessageQueueModifyInput `json:"messageQueueInput"` +} + +// GetMessageQueueInput returns __messageQueueModifyInput.MessageQueueInput, and is useful for accessing the field via an interface. +func (v *__messageQueueModifyInput) GetMessageQueueInput() MessageQueueModifyInput { + return v.MessageQueueInput +} + // __messageQueueUserCredentialsGetInput is used internally by genqlient type __messageQueueUserCredentialsGetInput struct { MessageQueueInput MessageQueueResourceInput `json:"messageQueueInput"` @@ -3676,6 +3819,17 @@ type messageQueueGetResponse struct { // GetMessageQueue returns messageQueueGetResponse.MessageQueue, and is useful for accessing the field via an interface. func (v *messageQueueGetResponse) GetMessageQueue() MessageQueueResult { return v.MessageQueue } +// messageQueueModifyResponse is returned by messageQueueModify on success. +type messageQueueModifyResponse struct { + // Cost: complexity = 100, multipliers = [], defaultMultiplier = null + MessageQueueModify MessageQueueResult `json:"messageQueueModify"` +} + +// GetMessageQueueModify returns messageQueueModifyResponse.MessageQueueModify, and is useful for accessing the field via an interface. +func (v *messageQueueModifyResponse) GetMessageQueueModify() MessageQueueResult { + return v.MessageQueueModify +} + // messageQueuePlansGetResponse is returned by messageQueuePlansGet on success. type messageQueuePlansGetResponse struct { // Cost: complexity = 100, multipliers = [], defaultMultiplier = null @@ -5885,6 +6039,19 @@ fragment MessageQueueResult on MessageQueue { role status } + externalConnection { + ... ExternalConnectionResult + } +} +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } } ` @@ -5965,6 +6132,19 @@ fragment MessageQueueResult on MessageQueue { role status } + externalConnection { + ... ExternalConnectionResult + } +} +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } } ` @@ -5993,6 +6173,67 @@ func messageQueueGet( return data_, err_ } +// The mutation executed by messageQueueModify. +const messageQueueModify_Operation = ` +mutation messageQueueModify ($messageQueueInput: MessageQueueModifyInput!) { + messageQueueModify(messageQueue: $messageQueueInput) { + ... MessageQueueResult + } +} +fragment MessageQueueResult on MessageQueue { + id + locked + name + state + namespace { + name + } + adminUser { + name + role + status + } + externalConnection { + ... ExternalConnectionResult + } +} +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } +} +` + +func messageQueueModify( + ctx_ context.Context, + client_ graphql.Client, + messageQueueInput MessageQueueModifyInput, +) (data_ *messageQueueModifyResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "messageQueueModify", + Query: messageQueueModify_Operation, + Variables: &__messageQueueModifyInput{ + MessageQueueInput: messageQueueInput, + }, + } + + data_ = &messageQueueModifyResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + // The query executed by messageQueuePlansGet. const messageQueuePlansGet_Operation = ` query messageQueuePlansGet { @@ -6134,6 +6375,19 @@ fragment MessageQueueResult on MessageQueue { role status } + externalConnection { + ... ExternalConnectionResult + } +} +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } } ` diff --git a/api/messageQueue.go b/api/messageQueue.go index 66b6c09..25e45f7 100644 --- a/api/messageQueue.go +++ b/api/messageQueue.go @@ -31,6 +31,14 @@ func (client *Client) MessageQueueCreate(input MessageQueueCreateInput) (Message return resp.GetMessageQueueCreate(), nil } +func (client *Client) MessageQueueModify(input MessageQueueModifyInput) (MessageQueueResult, error) { + resp, err := messageQueueModify(context.Background(), *client.client, input) + if err != nil { + return MessageQueueResult{}, err + } + return resp.GetMessageQueueModify(), nil +} + func (client *Client) MessageQueueDelete(input MessageQueueResourceInput) (bool, error) { resp, err := messageQueueDelete(context.Background(), *client.client, input) if err != nil { diff --git a/cmd/messageQueue.go b/cmd/messageQueue.go index 9e2f774..886cd49 100644 --- a/cmd/messageQueue.go +++ b/cmd/messageQueue.go @@ -275,4 +275,6 @@ func init() { listAdminUserCredentialsCmd.MarkFlagRequired("name") listAdminUserCredentialsCmd.MarkFlagRequired("username") messageQueueCmd.AddCommand(listAdminUserCredentialsCmd) + + messageQueueCmd.AddCommand(messageQueueEnableExternalConnectionCmd) } diff --git a/go.sum b/go.sum index e79d515..ec00107 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,15 @@ github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= +github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= +github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= +github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -42,15 +50,22 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k= github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 189280c..6450f21 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,10 @@ package main import ( + "crypto/tls" "fmt" "log" + "net/http" "os" "github.com/nexaa-cloud/nexaa-cli/cmd" @@ -10,6 +12,7 @@ import ( ) func main() { + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} config.Initialize() if err := config.LoadConfig(); err != nil { diff --git a/operations/MessageQueue.graphql b/operations/MessageQueue.graphql index 354f3e2..ffa6c5e 100644 --- a/operations/MessageQueue.graphql +++ b/operations/MessageQueue.graphql @@ -11,6 +11,9 @@ fragment MessageQueueResult on MessageQueue { role status } + externalConnection { + ...ExternalConnectionResult + } } fragment MessageQueuePlanResult on MessageQueuePlan { @@ -61,6 +64,14 @@ mutation messageQueueCreate($messageQueueInput: MessageQueueCreateInput!) { ...MessageQueueResult } } + +mutation messageQueueModify($messageQueueInput: MessageQueueModifyInput!) { + # @genqlient(flatten: true) + messageQueueModify(messageQueue: $messageQueueInput) { + ...MessageQueueResult + } +} + mutation messageQueueDelete($messageQueueInput: MessageQueueResourceInput!) { messageQueueDelete(messageQueue: $messageQueueInput) } diff --git a/schema.graphql b/schema.graphql index fbbbd32..9dd2166 100644 --- a/schema.graphql +++ b/schema.graphql @@ -88,10 +88,64 @@ type CloudDatabase { whitelist: [String!]! } +""" +Enable this feature to get recommendations on how to improve your database usage. + +The tool will be providing you settings on a cluster level to tune your database, provide recommendations for +missing indexes and more. + +Database clusters will be analyzed once a day. +""" +input CloudDatabaseAdvisorInput { + "Set this field to `true` to enable recommendations on how to improve your database usage." + enabled: Boolean! = false +} + +type CloudDatabaseAdvisory implements CloudDatabaseAdvisoryInterface { + createdAt: String! + identifier: String! + message: String! +} + +interface CloudDatabaseAdvisoryInterface { + createdAt: String! + identifier: String! + message: String! +} + +type CloudDatabaseAdvisoryMissingIndex implements CloudDatabaseAdvisoryInterface { + createdAt: String! + databaseName: String! + identifier: String! + message: String! +} + +enum CloudDatabaseAdvisoryType { + MissingIndex + SettingSuggestion +} + type CloudDatabaseCluster { adminUser: DatabaseUser + + """ + + Cost: complexity = 10, multipliers = [type], defaultMultiplier = null + """ + advisories(type: CloudDatabaseAdvisoryType = null): [CloudDatabaseAdvisoryInterface!]! + databases: [Database!]! externalConnection: ExternalConnection + + """ + Returns the features available on a cluster. + Features are extra functionality or capabilities for database clusters. + They are opt-in and provide extra services for your database. + + Cost: complexity = 10, multipliers = [], defaultMultiplier = null + """ + features: [CloudDatabaseClusterFeature!]! + hostname: String! id: String! locked: Boolean! @@ -104,6 +158,17 @@ type CloudDatabaseCluster { users: [DatabaseUser!]! } +type CloudDatabaseClusterAdvisor implements CloudDatabaseClusterFeature { + enabled: Boolean! + status: String! +} + +""" +Input for create cloud database cluster. + +A database cluster is accessible from within the namespace your created it in. +There is no limit on the number of users, or database. +""" input CloudDatabaseClusterCreateInput { "" name: String! @@ -123,6 +188,9 @@ input CloudDatabaseClusterCreateInput { "" users: [DatabaseUserInput!] = null + "" + advisor: CloudDatabaseAdvisorInput = null + "" externalConnection: ExternalConnectionInput = null } @@ -143,6 +211,22 @@ input CloudDatabaseClusterDatabaseResourceInput { name: String! } +interface CloudDatabaseClusterFeature { + enabled: Boolean! + status: String! +} + +type CloudDatabaseClusterFeatureImpl implements CloudDatabaseClusterFeature { + enabled: Boolean! + status: String! +} + +""" +Input for create cloud database cluster. + +A database cluster is accessible from within the namespace your created it in. +There is no limit on the number of users, or database. +""" input CloudDatabaseClusterModifyInput { "" name: String! @@ -156,6 +240,9 @@ input CloudDatabaseClusterModifyInput { "" users: [DatabaseUserInput!] + "" + advisor: CloudDatabaseAdvisorInput + "" externalConnection: ExternalConnectionInput } @@ -207,6 +294,7 @@ type Container { containerType: ContainerType! @deprecated(reason: "use `type` instead") createdAt: DateTime! deletedAt: DateTime @deprecated(reason: "this field will be removed in the future, and doesn't have a replacement") + endpoints: [ContainerEndpoint!]! entrypoint: [String!] environmentVariables: [EnvironmentVariable!]! healthCheck: HealthCheck @@ -310,6 +398,15 @@ input ContainerDeleteInput { namespace: String! } +type ContainerEndpoint { + allowList: [String!]! + containerPort: Int! + id: String! + ipv4Address: String! + ipv6Address: String! + port: Int! +} + type ContainerJob { command: [String!] enabled: Boolean! @@ -775,6 +872,14 @@ input DeleteRegistryConnectionInput { namespace: String! } +type Endpoint { + allowList: [String!]! + id: String! + ipv4Address: String! + ipv6Address: String! + port: Int! +} + type EnvironmentVariable { id: ID! @deprecated(reason: "environment variables are identified by name") name: String! @@ -814,7 +919,7 @@ input ExternalConnectionInput { state: State! = PRESENT "" - ports: [ExternalConnectionPortInput!] = [] + ports: [ExternalConnectionPortInput!]! = [] } type ExternalConnectionPort { @@ -886,6 +991,7 @@ input ManualScalingInput { type MessageQueue { adminUser: MessageQueueUser + externalConnection: ExternalConnection id: String! ingress: MessageQueueIngress! locked: Boolean! @@ -911,6 +1017,9 @@ input MessageQueueCreateInput { "" allowList: [AllowListInput!]! + + "" + externalConnection: ExternalConnectionInput = null } type MessageQueueIngress { @@ -921,6 +1030,20 @@ type MessageQueueIngress { tlsEnabled: Boolean! } +input MessageQueueModifyInput { + "" + name: String! + + "" + namespace: String! + + "" + allowList: [AllowListInput!] + + "" + externalConnection: ExternalConnectionInput +} + type MessageQueuePlan { benchmark: MessageQueuePlanBenchmark cpu(unit: CpuUnit = CPU): Float! @@ -1138,7 +1261,7 @@ type Mutation { Cost: complexity = 100, multipliers = [], defaultMultiplier = null """ - messageQueueCreate(messageQueue: MessageQueueCreateInput!): MessageQueue! + messageQueueCreate(messageQueue: MessageQueueCreateInput): MessageQueue! """ @@ -1150,7 +1273,7 @@ type Mutation { Cost: complexity = 100, multipliers = [], defaultMultiplier = null """ - messageQueueModify(messageQueue: MessageQueueCreateInput!): MessageQueue! + messageQueueModify(messageQueue: MessageQueueModifyInput): MessageQueue! """ @@ -1582,4 +1705,3 @@ input VolumeResourceInput { "" namespace: String! } - From 409cf77e1ea2150245584c07358bcdab0518ca2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A8lios=20Stigter?= Date: Fri, 6 Mar 2026 10:17:11 +0100 Subject: [PATCH 05/13] Removed the http transport and added message queue external connection --- cmd/messageQueueExternalConnection.go | 97 +++++++++++++++++++++++++++ main.go | 3 - 2 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 cmd/messageQueueExternalConnection.go diff --git a/cmd/messageQueueExternalConnection.go b/cmd/messageQueueExternalConnection.go new file mode 100644 index 0000000..d9aa5d1 --- /dev/null +++ b/cmd/messageQueueExternalConnection.go @@ -0,0 +1,97 @@ +package cmd + +import ( + "fmt" + "log" + + "github.com/nexaa-cloud/nexaa-cli/api" + "github.com/spf13/cobra" +) + +var messageQueueEnableExternalConnectionCmd = &cobra.Command{ + Use: "external-connection", + Short: "Enable or Disable external connection on a cloud database cluster", +} + +var enableMessageQueueExternalConnectionCmd = &cobra.Command{ + Use: "enable", + Short: "Enable external connection on a cloud database cluster", + Run: func(cmd *cobra.Command, args []string) { + namespace, _ := cmd.Flags().GetString("namespace") + clusterName, _ := cmd.Flags().GetString("cluster") + allowedIp, _ := cmd.Flags().GetStringArray("allowed-ip") + + allowList := make([]api.AllowListInput, 0) + for _, ip := range allowedIp { + allowList = append(allowList, api.AllowListInput{Ip: ip, State: api.StatePresent}) + } + + resource := api.MessageQueueModifyInput{ + Name: clusterName, + Namespace: namespace, + ExternalConnection: &api.ExternalConnectionInput{ + State: api.StatePresent, + SharedIp: true, + Ports: []api.ExternalConnectionPortInput{ + { + AllowList: allowList, + State: api.StatePresent, + }, + }, + }, + } + + client := api.NewClient() + + cluster, err := client.MessageQueueModify(resource) + if err != nil { + log.Fatalf("Failed to enable external connection in cluster %q/%q: %v", namespace, clusterName, err) + return + } + fmt.Printf("External connection enabled. Reachable at:\n") + fmt.Printf("Ipv4: %s:%d \n", cluster.ExternalConnection.Ipv4, cluster.ExternalConnection.Ports[0].ExternalPort) + fmt.Printf("Ipv6: %s:%d \n", cluster.ExternalConnection.Ipv6, cluster.ExternalConnection.Ports[0].ExternalPort) + }, +} + +var disableMessageQueueExternalConnectionCmd = &cobra.Command{ + Use: "disable", + Short: "disable external connection on a cloud database cluster", + Run: func(cmd *cobra.Command, args []string) { + namespace, _ := cmd.Flags().GetString("namespace") + clusterName, _ := cmd.Flags().GetString("cluster") + + resource := api.MessageQueueModifyInput{ + Name: clusterName, + Namespace: namespace, + ExternalConnection: &api.ExternalConnectionInput{ + State: api.StateAbsent, + Ports: []api.ExternalConnectionPortInput{}, + }, + } + + client := api.NewClient() + + cluster, err := client.MessageQueueModify(resource) + if err != nil { + log.Fatalf("Failed to disable external connection in cluster %q/%q: %v", namespace, clusterName, err) + return + } + fmt.Printf("External connection disabled in: %s/%s. \n", cluster.Namespace.Name, cluster.Name) + }, +} + +func init() { + enableMessageQueueExternalConnectionCmd.Flags().String("namespace", "", "Namespace") + enableMessageQueueExternalConnectionCmd.Flags().String("cluster", "", "Name of the cluster") + enableMessageQueueExternalConnectionCmd.Flags().StringArray("allowed-ip", []string{"0.0.0.0/0", "::/0"}, "Allowed ip for the connection") + enableMessageQueueExternalConnectionCmd.MarkFlagRequired("namespace") + enableMessageQueueExternalConnectionCmd.MarkFlagRequired("cluster") + messageQueueEnableExternalConnectionCmd.AddCommand(enableMessageQueueExternalConnectionCmd) + + disableMessageQueueExternalConnectionCmd.Flags().String("namespace", "", "Namespace") + disableMessageQueueExternalConnectionCmd.Flags().String("cluster", "", "Name of the cluster") + disableMessageQueueExternalConnectionCmd.MarkFlagRequired("namespace") + disableMessageQueueExternalConnectionCmd.MarkFlagRequired("cluster") + messageQueueEnableExternalConnectionCmd.AddCommand(disableMessageQueueExternalConnectionCmd) +} diff --git a/main.go b/main.go index 6450f21..189280c 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,8 @@ package main import ( - "crypto/tls" "fmt" "log" - "net/http" "os" "github.com/nexaa-cloud/nexaa-cli/cmd" @@ -12,7 +10,6 @@ import ( ) func main() { - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} config.Initialize() if err := config.LoadConfig(); err != nil { From 85d887c46637d24d67d792736dcbce5592509c5c Mon Sep 17 00:00:00 2001 From: Matthijs van Gastel Date: Tue, 10 Mar 2026 13:30:12 +0100 Subject: [PATCH 06/13] Added spec and plan to message queue result in the generated.go --- api/generated.go | 282 ++++++++++++++++++++++++++++++++ operations/MessageQueue.graphql | 6 + 2 files changed, 288 insertions(+) diff --git a/api/generated.go b/api/generated.go index eb2ac0b..2c9e4d0 100644 --- a/api/generated.go +++ b/api/generated.go @@ -1978,6 +1978,8 @@ type MessageQueueResult struct { State string `json:"state"` Namespace MessageQueueResultNamespace `json:"namespace"` AdminUser *MessageQueueResultAdminUserMessageQueueUser `json:"adminUser"` + Plan MessageQueueResultPlanMessageQueuePlan `json:"plan"` + Spec MessageQueueResultSpecMessageQueueSpec `json:"spec"` ExternalConnection *MessageQueueResultExternalConnection `json:"externalConnection"` } @@ -2001,6 +2003,12 @@ func (v *MessageQueueResult) GetAdminUser() *MessageQueueResultAdminUserMessageQ return v.AdminUser } +// GetPlan returns MessageQueueResult.Plan, and is useful for accessing the field via an interface. +func (v *MessageQueueResult) GetPlan() MessageQueueResultPlanMessageQueuePlan { return v.Plan } + +// GetSpec returns MessageQueueResult.Spec, and is useful for accessing the field via an interface. +func (v *MessageQueueResult) GetSpec() MessageQueueResultSpecMessageQueueSpec { return v.Spec } + // GetExternalConnection returns MessageQueueResult.ExternalConnection, and is useful for accessing the field via an interface. func (v *MessageQueueResult) GetExternalConnection() *MessageQueueResultExternalConnection { return v.ExternalConnection @@ -2100,6 +2108,184 @@ type MessageQueueResultNamespace struct { // GetName returns MessageQueueResultNamespace.Name, and is useful for accessing the field via an interface. func (v *MessageQueueResultNamespace) GetName() string { return v.Name } +// MessageQueueResultPlanMessageQueuePlan includes the requested fields of the GraphQL type MessageQueuePlan. +type MessageQueueResultPlanMessageQueuePlan struct { + MessageQueuePlanResult `json:"-"` +} + +// GetCpu returns MessageQueueResultPlanMessageQueuePlan.Cpu, and is useful for accessing the field via an interface. +func (v *MessageQueueResultPlanMessageQueuePlan) GetCpu() float64 { + return v.MessageQueuePlanResult.Cpu +} + +// GetGroup returns MessageQueueResultPlanMessageQueuePlan.Group, and is useful for accessing the field via an interface. +func (v *MessageQueueResultPlanMessageQueuePlan) GetGroup() string { + return v.MessageQueuePlanResult.Group +} + +// GetId returns MessageQueueResultPlanMessageQueuePlan.Id, and is useful for accessing the field via an interface. +func (v *MessageQueueResultPlanMessageQueuePlan) GetId() string { return v.MessageQueuePlanResult.Id } + +// GetMemory returns MessageQueueResultPlanMessageQueuePlan.Memory, and is useful for accessing the field via an interface. +func (v *MessageQueueResultPlanMessageQueuePlan) GetMemory() float64 { + return v.MessageQueuePlanResult.Memory +} + +// GetName returns MessageQueueResultPlanMessageQueuePlan.Name, and is useful for accessing the field via an interface. +func (v *MessageQueueResultPlanMessageQueuePlan) GetName() string { + return v.MessageQueuePlanResult.Name +} + +// GetPrice returns MessageQueueResultPlanMessageQueuePlan.Price, and is useful for accessing the field via an interface. +func (v *MessageQueueResultPlanMessageQueuePlan) GetPrice() MessageQueuePlanResultPrice { + return v.MessageQueuePlanResult.Price +} + +// GetReplicas returns MessageQueueResultPlanMessageQueuePlan.Replicas, and is useful for accessing the field via an interface. +func (v *MessageQueueResultPlanMessageQueuePlan) GetReplicas() int { + return v.MessageQueuePlanResult.Replicas +} + +// GetStorage returns MessageQueueResultPlanMessageQueuePlan.Storage, and is useful for accessing the field via an interface. +func (v *MessageQueueResultPlanMessageQueuePlan) GetStorage() float64 { + return v.MessageQueuePlanResult.Storage +} + +func (v *MessageQueueResultPlanMessageQueuePlan) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *MessageQueueResultPlanMessageQueuePlan + graphql.NoUnmarshalJSON + } + firstPass.MessageQueueResultPlanMessageQueuePlan = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MessageQueuePlanResult) + if err != nil { + return err + } + return nil +} + +type __premarshalMessageQueueResultPlanMessageQueuePlan struct { + Cpu float64 `json:"cpu"` + + Group string `json:"group"` + + Id string `json:"id"` + + Memory float64 `json:"memory"` + + Name string `json:"name"` + + Price MessageQueuePlanResultPrice `json:"price"` + + Replicas int `json:"replicas"` + + Storage float64 `json:"storage"` +} + +func (v *MessageQueueResultPlanMessageQueuePlan) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *MessageQueueResultPlanMessageQueuePlan) __premarshalJSON() (*__premarshalMessageQueueResultPlanMessageQueuePlan, error) { + var retval __premarshalMessageQueueResultPlanMessageQueuePlan + + retval.Cpu = v.MessageQueuePlanResult.Cpu + retval.Group = v.MessageQueuePlanResult.Group + retval.Id = v.MessageQueuePlanResult.Id + retval.Memory = v.MessageQueuePlanResult.Memory + retval.Name = v.MessageQueuePlanResult.Name + retval.Price = v.MessageQueuePlanResult.Price + retval.Replicas = v.MessageQueuePlanResult.Replicas + retval.Storage = v.MessageQueuePlanResult.Storage + return &retval, nil +} + +// MessageQueueResultSpecMessageQueueSpec includes the requested fields of the GraphQL type MessageQueueSpec. +type MessageQueueResultSpecMessageQueueSpec struct { + MessageQueueVersionResult `json:"-"` +} + +// GetPatchLevelVersion returns MessageQueueResultSpecMessageQueueSpec.PatchLevelVersion, and is useful for accessing the field via an interface. +func (v *MessageQueueResultSpecMessageQueueSpec) GetPatchLevelVersion() string { + return v.MessageQueueVersionResult.PatchLevelVersion +} + +// GetType returns MessageQueueResultSpecMessageQueueSpec.Type, and is useful for accessing the field via an interface. +func (v *MessageQueueResultSpecMessageQueueSpec) GetType() string { + return v.MessageQueueVersionResult.Type +} + +// GetVersion returns MessageQueueResultSpecMessageQueueSpec.Version, and is useful for accessing the field via an interface. +func (v *MessageQueueResultSpecMessageQueueSpec) GetVersion() string { + return v.MessageQueueVersionResult.Version +} + +func (v *MessageQueueResultSpecMessageQueueSpec) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *MessageQueueResultSpecMessageQueueSpec + graphql.NoUnmarshalJSON + } + firstPass.MessageQueueResultSpecMessageQueueSpec = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MessageQueueVersionResult) + if err != nil { + return err + } + return nil +} + +type __premarshalMessageQueueResultSpecMessageQueueSpec struct { + PatchLevelVersion string `json:"patchLevelVersion"` + + Type string `json:"type"` + + Version string `json:"version"` +} + +func (v *MessageQueueResultSpecMessageQueueSpec) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *MessageQueueResultSpecMessageQueueSpec) __premarshalJSON() (*__premarshalMessageQueueResultSpecMessageQueueSpec, error) { + var retval __premarshalMessageQueueResultSpecMessageQueueSpec + + retval.PatchLevelVersion = v.MessageQueueVersionResult.PatchLevelVersion + retval.Type = v.MessageQueueVersionResult.Type + retval.Version = v.MessageQueueVersionResult.Version + return &retval, nil +} + type MessageQueueSpecInput struct { Type string `json:"type"` Version string `json:"version"` @@ -6039,10 +6225,34 @@ fragment MessageQueueResult on MessageQueue { role status } + plan { + ... MessageQueuePlanResult + } + spec { + ... MessageQueueVersionResult + } externalConnection { ... ExternalConnectionResult } } +fragment MessageQueuePlanResult on MessageQueuePlan { + cpu(unit: CPU) + group + id + memory(unit: GB) + name + price { + amount + currency + } + replicas + storage(unit: GB) +} +fragment MessageQueueVersionResult on MessageQueueSpec { + patchLevelVersion + type + version +} fragment ExternalConnectionResult on ExternalConnection { ipv4 ipv6 @@ -6132,10 +6342,34 @@ fragment MessageQueueResult on MessageQueue { role status } + plan { + ... MessageQueuePlanResult + } + spec { + ... MessageQueueVersionResult + } externalConnection { ... ExternalConnectionResult } } +fragment MessageQueuePlanResult on MessageQueuePlan { + cpu(unit: CPU) + group + id + memory(unit: GB) + name + price { + amount + currency + } + replicas + storage(unit: GB) +} +fragment MessageQueueVersionResult on MessageQueueSpec { + patchLevelVersion + type + version +} fragment ExternalConnectionResult on ExternalConnection { ipv4 ipv6 @@ -6193,10 +6427,34 @@ fragment MessageQueueResult on MessageQueue { role status } + plan { + ... MessageQueuePlanResult + } + spec { + ... MessageQueueVersionResult + } externalConnection { ... ExternalConnectionResult } } +fragment MessageQueuePlanResult on MessageQueuePlan { + cpu(unit: CPU) + group + id + memory(unit: GB) + name + price { + amount + currency + } + replicas + storage(unit: GB) +} +fragment MessageQueueVersionResult on MessageQueueSpec { + patchLevelVersion + type + version +} fragment ExternalConnectionResult on ExternalConnection { ipv4 ipv6 @@ -6375,10 +6633,34 @@ fragment MessageQueueResult on MessageQueue { role status } + plan { + ... MessageQueuePlanResult + } + spec { + ... MessageQueueVersionResult + } externalConnection { ... ExternalConnectionResult } } +fragment MessageQueuePlanResult on MessageQueuePlan { + cpu(unit: CPU) + group + id + memory(unit: GB) + name + price { + amount + currency + } + replicas + storage(unit: GB) +} +fragment MessageQueueVersionResult on MessageQueueSpec { + patchLevelVersion + type + version +} fragment ExternalConnectionResult on ExternalConnection { ipv4 ipv6 diff --git a/operations/MessageQueue.graphql b/operations/MessageQueue.graphql index ffa6c5e..590a617 100644 --- a/operations/MessageQueue.graphql +++ b/operations/MessageQueue.graphql @@ -11,6 +11,12 @@ fragment MessageQueueResult on MessageQueue { role status } + plan { + ...MessageQueuePlanResult + } + spec { + ...MessageQueueVersionResult + } externalConnection { ...ExternalConnectionResult } From c690051f76ca64bfc18441755e868035f88b2cd4 Mon Sep 17 00:00:00 2001 From: Matthijs van Gastel Date: Tue, 10 Mar 2026 13:52:07 +0100 Subject: [PATCH 07/13] added allowlist of the management console to the result --- operations/MessageQueue.graphql | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/operations/MessageQueue.graphql b/operations/MessageQueue.graphql index 590a617..8081772 100644 --- a/operations/MessageQueue.graphql +++ b/operations/MessageQueue.graphql @@ -17,6 +17,9 @@ fragment MessageQueueResult on MessageQueue { spec { ...MessageQueueVersionResult } + ingress { + ...MessageQueueIngressResult + } externalConnection { ...ExternalConnectionResult } @@ -36,6 +39,10 @@ fragment MessageQueuePlanResult on MessageQueuePlan { storage(unit: GB) } +fragment MessageQueueIngressResult on MessageQueueIngress { + allowList +} + fragment MessageQueueVersionResult on MessageQueueSpec { patchLevelVersion type From 0580f31791d1719d8f24ac5a6d780ecf03bd415c Mon Sep 17 00:00:00 2001 From: Matthijs van Gastel Date: Tue, 10 Mar 2026 14:01:35 +0100 Subject: [PATCH 08/13] Added allowlistresult in the generated.go --- api/generated.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/api/generated.go b/api/generated.go index 2c9e4d0..57fdefc 100644 --- a/api/generated.go +++ b/api/generated.go @@ -1890,6 +1890,14 @@ func (v *MessageQueueCreateInput) GetExternalConnection() *ExternalConnectionInp return v.ExternalConnection } +// MessageQueueIngressResult includes the GraphQL fields of MessageQueueIngress requested by the fragment MessageQueueIngressResult. +type MessageQueueIngressResult struct { + AllowList []string `json:"allowList"` +} + +// GetAllowList returns MessageQueueIngressResult.AllowList, and is useful for accessing the field via an interface. +func (v *MessageQueueIngressResult) GetAllowList() []string { return v.AllowList } + type MessageQueueModifyInput struct { Name string `json:"name"` Namespace string `json:"namespace"` @@ -1980,6 +1988,7 @@ type MessageQueueResult struct { AdminUser *MessageQueueResultAdminUserMessageQueueUser `json:"adminUser"` Plan MessageQueueResultPlanMessageQueuePlan `json:"plan"` Spec MessageQueueResultSpecMessageQueueSpec `json:"spec"` + Ingress MessageQueueResultIngressMessageQueueIngress `json:"ingress"` ExternalConnection *MessageQueueResultExternalConnection `json:"externalConnection"` } @@ -2009,6 +2018,11 @@ func (v *MessageQueueResult) GetPlan() MessageQueueResultPlanMessageQueuePlan { // GetSpec returns MessageQueueResult.Spec, and is useful for accessing the field via an interface. func (v *MessageQueueResult) GetSpec() MessageQueueResultSpecMessageQueueSpec { return v.Spec } +// GetIngress returns MessageQueueResult.Ingress, and is useful for accessing the field via an interface. +func (v *MessageQueueResult) GetIngress() MessageQueueResultIngressMessageQueueIngress { + return v.Ingress +} + // GetExternalConnection returns MessageQueueResult.ExternalConnection, and is useful for accessing the field via an interface. func (v *MessageQueueResult) GetExternalConnection() *MessageQueueResultExternalConnection { return v.ExternalConnection @@ -2100,6 +2114,60 @@ func (v *MessageQueueResultExternalConnection) __premarshalJSON() (*__premarshal return &retval, nil } +// MessageQueueResultIngressMessageQueueIngress includes the requested fields of the GraphQL type MessageQueueIngress. +type MessageQueueResultIngressMessageQueueIngress struct { + MessageQueueIngressResult `json:"-"` +} + +// GetAllowList returns MessageQueueResultIngressMessageQueueIngress.AllowList, and is useful for accessing the field via an interface. +func (v *MessageQueueResultIngressMessageQueueIngress) GetAllowList() []string { + return v.MessageQueueIngressResult.AllowList +} + +func (v *MessageQueueResultIngressMessageQueueIngress) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *MessageQueueResultIngressMessageQueueIngress + graphql.NoUnmarshalJSON + } + firstPass.MessageQueueResultIngressMessageQueueIngress = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MessageQueueIngressResult) + if err != nil { + return err + } + return nil +} + +type __premarshalMessageQueueResultIngressMessageQueueIngress struct { + AllowList []string `json:"allowList"` +} + +func (v *MessageQueueResultIngressMessageQueueIngress) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *MessageQueueResultIngressMessageQueueIngress) __premarshalJSON() (*__premarshalMessageQueueResultIngressMessageQueueIngress, error) { + var retval __premarshalMessageQueueResultIngressMessageQueueIngress + + retval.AllowList = v.MessageQueueIngressResult.AllowList + return &retval, nil +} + // MessageQueueResultNamespace includes the requested fields of the GraphQL type Namespace. type MessageQueueResultNamespace struct { Name string `json:"name"` @@ -6231,6 +6299,9 @@ fragment MessageQueueResult on MessageQueue { spec { ... MessageQueueVersionResult } + ingress { + ... MessageQueueIngressResult + } externalConnection { ... ExternalConnectionResult } @@ -6253,6 +6324,9 @@ fragment MessageQueueVersionResult on MessageQueueSpec { type version } +fragment MessageQueueIngressResult on MessageQueueIngress { + allowList +} fragment ExternalConnectionResult on ExternalConnection { ipv4 ipv6 @@ -6348,6 +6422,9 @@ fragment MessageQueueResult on MessageQueue { spec { ... MessageQueueVersionResult } + ingress { + ... MessageQueueIngressResult + } externalConnection { ... ExternalConnectionResult } @@ -6370,6 +6447,9 @@ fragment MessageQueueVersionResult on MessageQueueSpec { type version } +fragment MessageQueueIngressResult on MessageQueueIngress { + allowList +} fragment ExternalConnectionResult on ExternalConnection { ipv4 ipv6 @@ -6433,6 +6513,9 @@ fragment MessageQueueResult on MessageQueue { spec { ... MessageQueueVersionResult } + ingress { + ... MessageQueueIngressResult + } externalConnection { ... ExternalConnectionResult } @@ -6455,6 +6538,9 @@ fragment MessageQueueVersionResult on MessageQueueSpec { type version } +fragment MessageQueueIngressResult on MessageQueueIngress { + allowList +} fragment ExternalConnectionResult on ExternalConnection { ipv4 ipv6 @@ -6639,6 +6725,9 @@ fragment MessageQueueResult on MessageQueue { spec { ... MessageQueueVersionResult } + ingress { + ... MessageQueueIngressResult + } externalConnection { ... ExternalConnectionResult } @@ -6661,6 +6750,9 @@ fragment MessageQueueVersionResult on MessageQueueSpec { type version } +fragment MessageQueueIngressResult on MessageQueueIngress { + allowList +} fragment ExternalConnectionResult on ExternalConnection { ipv4 ipv6 From 0c1ae72a8591568e0313b5ce63c2a9bce0d33657 Mon Sep 17 00:00:00 2001 From: Jaap van Otterdijk Date: Wed, 11 Mar 2026 13:20:47 +0100 Subject: [PATCH 09/13] Setup external connections on containers. --- api/container.go | 110 +-------- api/generated.go | 220 ++++++++++++++++-- cmd/cloudDatabaseCluster.go | 2 +- cmd/cloudDatabaseClusterDatabase.go | 6 +- cmd/cloudDatabaseClusterExternalConnection.go | 4 +- cmd/container.go | 18 +- cmd/containerExternalConnection.go | 208 +++++++++++++++++ cmd/container_job.go | 10 +- cmd/messageQueue.go | 8 +- cmd/messageQueueExternalConnection.go | 4 +- cmd/registry.go | 6 +- cmd/volume.go | 8 +- operations/Container.graphql | 3 + schema.graphql | 30 ++- 14 files changed, 475 insertions(+), 162 deletions(-) create mode 100644 cmd/containerExternalConnection.go diff --git a/api/container.go b/api/container.go index 1c2aabc..54bac34 100644 --- a/api/container.go +++ b/api/container.go @@ -5,104 +5,6 @@ import ( "fmt" ) -func toContainerResult(container ContainerResult) (ContainerResult, error) { - // Environment Variables - var envVars []EnvironmentVariableResult - if container.EnvironmentVariables != nil { - envVars = make([]EnvironmentVariableResult, len(container.EnvironmentVariables)) - for j, ev := range container.EnvironmentVariables { - envVars[j] = EnvironmentVariableResult{ - Name: ev.Name, - Value: ev.Value, - Secret: ev.Secret, - } - } - } - - // Ingresses - var ingresses []ContainerResultIngressesIngress - if container.Ingresses != nil { - ingresses = make([]ContainerResultIngressesIngress, len(container.Ingresses)) - for j, v := range container.Ingresses { - ingresses[j] = ContainerResultIngressesIngress{ - DomainName: v.DomainName, - Port: v.Port, - EnableTLS: v.EnableTLS, - Allowlist: v.Allowlist, - State: v.State, - } - } - } - - // Mounts - var mounts []ContainerMounts - if container.Mounts != nil { - mounts = make([]ContainerMounts, len(container.Mounts)) - for j, m := range container.Mounts { - var volumeName string - if m.Volume.Name != "" { - volumeName = m.Volume.Name - } - mounts[j] = ContainerMounts{ - Path: m.Path, - Volume: ContainerMountsVolume{ - Name: volumeName, - }, - } - } - } - - // Health Check - var healthCheck *ContainerResultHealthCheck - if container.HealthCheck != nil { - healthCheck = &ContainerResultHealthCheck{ - Port: container.HealthCheck.Port, - Path: container.HealthCheck.Path, - } - } - - // Auto Scaling - var autoScaling *ContainerResultAutoScaling - if container.AutoScaling != nil { - var triggers []ContainerResultAutoScalingTriggersAutoScalingTrigger - if container.AutoScaling.Triggers != nil { - triggers = make([]ContainerResultAutoScalingTriggersAutoScalingTrigger, len(container.AutoScaling.Triggers)) - for j, t := range container.AutoScaling.Triggers { - triggers[j] = ContainerResultAutoScalingTriggersAutoScalingTrigger{ - Type: t.Type, - Threshold: t.Threshold, - } - } - } - autoScaling = &ContainerResultAutoScaling{ - Replicas: ContainerResultAutoScalingReplicas{ - Minimum: container.AutoScaling.Replicas.Minimum, - Maximum: container.AutoScaling.Replicas.Maximum, - }, - Triggers: triggers, - } - } - registry := container.PrivateRegistry - - return ContainerResult{ - Name: container.Name, - Image: container.Image, - PrivateRegistry: registry, - Resources: container.Resources, - EnvironmentVariables: envVars, - Ports: container.Ports, - Ingresses: ingresses, - Mounts: mounts, - HealthCheck: healthCheck, - NumberOfReplicas: container.NumberOfReplicas, - AutoScaling: autoScaling, - Locked: container.Locked, - State: container.State, - Command: container.Command, - Entrypoint: container.Entrypoint, - }, nil -} - func (client *Client) ListContainers(namespace string) ([]ContainerResult, error) { containerResponse, err := containerList(context.Background(), *client.client, namespace) if err != nil { @@ -110,11 +12,13 @@ func (client *Client) ListContainers(namespace string) ([]ContainerResult, error } namespaceResult := containerResponse.GetNamespace() - result := make([]ContainerResult, len(namespaceResult.Containers)) + if len(namespaceResult.Containers) == 0 { + return []ContainerResult{}, nil + } + result := make([]ContainerResult, 0, len(namespaceResult.Containers)) for _, container := range namespaceResult.Containers { - var c, _ = toContainerResult(container.ContainerResult) - result = append(result, c) + result = append(result, container.ContainerResult) } return result, nil @@ -130,9 +34,7 @@ func (client *Client) ListContainerByName(namespace string, containerName string return ContainerResult{}, fmt.Errorf("container %q not found in namespace %q", containerName, namespace) } - res, err := toContainerResult(container.Container) - - return res, err + return container.Container, err } func (client *Client) ContainerCreate(input ContainerCreateInput) (ContainerResult, error) { diff --git a/api/generated.go b/api/generated.go index 57fdefc..480d798 100644 --- a/api/generated.go +++ b/api/generated.go @@ -846,7 +846,8 @@ type ContainerCreateInput struct { // Example: `echo "Hello $(NAME)"`. // // This field is defined in docker exec format. https://docs.docker.com/reference/dockerfile/#shell-and-exec-form - Command []string `json:"command"` + Command []string `json:"command"` + ExternalConnection *ExternalConnectionInput `json:"externalConnection"` } // GetName returns ContainerCreateInput.Name, and is useful for accessing the field via an interface. @@ -893,6 +894,11 @@ func (v *ContainerCreateInput) GetEntrypoint() []string { return v.Entrypoint } // GetCommand returns ContainerCreateInput.Command, and is useful for accessing the field via an interface. func (v *ContainerCreateInput) GetCommand() []string { return v.Command } +// GetExternalConnection returns ContainerCreateInput.ExternalConnection, and is useful for accessing the field via an interface. +func (v *ContainerCreateInput) GetExternalConnection() *ExternalConnectionInput { + return v.ExternalConnection +} + type ContainerJobCreateInput struct { Name string `json:"name"` Namespace string `json:"namespace"` @@ -1164,7 +1170,8 @@ type ContainerModifyInput struct { // Example: `echo "Hello $(NAME)"`. // // This field is defined in docker exec format. https://docs.docker.com/reference/dockerfile/#shell-and-exec-form - Command []string `json:"command"` + Command []string `json:"command"` + ExternalConnection *ExternalConnectionInput `json:"externalConnection"` } // GetName returns ContainerModifyInput.Name, and is useful for accessing the field via an interface. @@ -1208,6 +1215,11 @@ func (v *ContainerModifyInput) GetEntrypoint() []string { return v.Entrypoint } // GetCommand returns ContainerModifyInput.Command, and is useful for accessing the field via an interface. func (v *ContainerModifyInput) GetCommand() []string { return v.Command } +// GetExternalConnection returns ContainerModifyInput.ExternalConnection, and is useful for accessing the field via an interface. +func (v *ContainerModifyInput) GetExternalConnection() *ExternalConnectionInput { + return v.ExternalConnection +} + // ContainerMounts includes the GraphQL fields of Mount requested by the fragment ContainerMounts. type ContainerMounts struct { Path string `json:"path"` @@ -1480,23 +1492,24 @@ var AllContainerResources = []ContainerResources{ // ContainerResult includes the GraphQL fields of Container requested by the fragment ContainerResult. type ContainerResult struct { - Name string `json:"name"` - Image string `json:"image"` - PrivateRegistry *ContainerResultPrivateRegistry `json:"privateRegistry"` - Resources ContainerResources `json:"resources"` - Command []string `json:"command"` - Entrypoint []string `json:"entrypoint"` - EnvironmentVariables []EnvironmentVariableResult `json:"environmentVariables"` - Ports []string `json:"ports"` - Ingresses []ContainerResultIngressesIngress `json:"ingresses"` - Mounts []ContainerMounts `json:"mounts"` - HealthCheck *ContainerResultHealthCheck `json:"healthCheck"` - AvailableReplicas int `json:"availableReplicas"` - NumberOfReplicas int `json:"numberOfReplicas"` - AutoScaling *ContainerResultAutoScaling `json:"autoScaling"` - State string `json:"state"` - Locked bool `json:"locked"` - Type ContainerType `json:"type"` + Name string `json:"name"` + Image string `json:"image"` + PrivateRegistry *ContainerResultPrivateRegistry `json:"privateRegistry"` + Resources ContainerResources `json:"resources"` + Command []string `json:"command"` + Entrypoint []string `json:"entrypoint"` + EnvironmentVariables []EnvironmentVariableResult `json:"environmentVariables"` + ExternalConnection *ContainerResultExternalConnection `json:"externalConnection"` + Ports []string `json:"ports"` + Ingresses []ContainerResultIngressesIngress `json:"ingresses"` + Mounts []ContainerMounts `json:"mounts"` + HealthCheck *ContainerResultHealthCheck `json:"healthCheck"` + AvailableReplicas int `json:"availableReplicas"` + NumberOfReplicas int `json:"numberOfReplicas"` + AutoScaling *ContainerResultAutoScaling `json:"autoScaling"` + State string `json:"state"` + Locked bool `json:"locked"` + Type ContainerType `json:"type"` } // GetName returns ContainerResult.Name, and is useful for accessing the field via an interface. @@ -1524,6 +1537,11 @@ func (v *ContainerResult) GetEnvironmentVariables() []EnvironmentVariableResult return v.EnvironmentVariables } +// GetExternalConnection returns ContainerResult.ExternalConnection, and is useful for accessing the field via an interface. +func (v *ContainerResult) GetExternalConnection() *ContainerResultExternalConnection { + return v.ExternalConnection +} + // GetPorts returns ContainerResult.Ports, and is useful for accessing the field via an interface. func (v *ContainerResult) GetPorts() []string { return v.Ports } @@ -1594,6 +1612,72 @@ func (v *ContainerResultAutoScalingTriggersAutoScalingTrigger) GetType() string // GetThreshold returns ContainerResultAutoScalingTriggersAutoScalingTrigger.Threshold, and is useful for accessing the field via an interface. func (v *ContainerResultAutoScalingTriggersAutoScalingTrigger) GetThreshold() int { return v.Threshold } +// ContainerResultExternalConnection includes the requested fields of the GraphQL type ExternalConnection. +type ContainerResultExternalConnection struct { + ExternalConnectionResult `json:"-"` +} + +// GetIpv4 returns ContainerResultExternalConnection.Ipv4, and is useful for accessing the field via an interface. +func (v *ContainerResultExternalConnection) GetIpv4() string { return v.ExternalConnectionResult.Ipv4 } + +// GetIpv6 returns ContainerResultExternalConnection.Ipv6, and is useful for accessing the field via an interface. +func (v *ContainerResultExternalConnection) GetIpv6() string { return v.ExternalConnectionResult.Ipv6 } + +// GetPorts returns ContainerResultExternalConnection.Ports, and is useful for accessing the field via an interface. +func (v *ContainerResultExternalConnection) GetPorts() []ExternalConnectionResultPortsExternalConnectionPort { + return v.ExternalConnectionResult.Ports +} + +func (v *ContainerResultExternalConnection) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *ContainerResultExternalConnection + graphql.NoUnmarshalJSON + } + firstPass.ContainerResultExternalConnection = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ExternalConnectionResult) + if err != nil { + return err + } + return nil +} + +type __premarshalContainerResultExternalConnection struct { + Ipv4 string `json:"ipv4"` + + Ipv6 string `json:"ipv6"` + + Ports []ExternalConnectionResultPortsExternalConnectionPort `json:"ports"` +} + +func (v *ContainerResultExternalConnection) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *ContainerResultExternalConnection) __premarshalJSON() (*__premarshalContainerResultExternalConnection, error) { + var retval __premarshalContainerResultExternalConnection + + retval.Ipv4 = v.ExternalConnectionResult.Ipv4 + retval.Ipv6 = v.ExternalConnectionResult.Ipv6 + retval.Ports = v.ExternalConnectionResult.Ports + return &retval, nil +} + // ContainerResultHealthCheck includes the requested fields of the GraphQL type HealthCheck. type ContainerResultHealthCheck struct { Port int `json:"port"` @@ -1762,14 +1846,28 @@ func (v *ExternalConnectionInput) GetState() State { return v.State } func (v *ExternalConnectionInput) GetPorts() []ExternalConnectionPortInput { return v.Ports } type ExternalConnectionPortInput struct { - ExternalPort *int `json:"externalPort"` - State State `json:"state"` - AllowList []AllowListInput `json:"allowList"` + ExternalPort *int `json:"externalPort"` + // Internal port is only used for external connections of containers. + // For other resources, it's automatically selected + // + // See ports in `ContainerCreateInput` and `ContainerModifyInput` + InternalPort *int `json:"internalPort"` + // Protocol is only used for external connections of containers. + // For other resources, only TCP is allowed. + Protocol Protocol `json:"protocol"` + State State `json:"state"` + AllowList []AllowListInput `json:"allowList"` } // GetExternalPort returns ExternalConnectionPortInput.ExternalPort, and is useful for accessing the field via an interface. func (v *ExternalConnectionPortInput) GetExternalPort() *int { return v.ExternalPort } +// GetInternalPort returns ExternalConnectionPortInput.InternalPort, and is useful for accessing the field via an interface. +func (v *ExternalConnectionPortInput) GetInternalPort() *int { return v.InternalPort } + +// GetProtocol returns ExternalConnectionPortInput.Protocol, and is useful for accessing the field via an interface. +func (v *ExternalConnectionPortInput) GetProtocol() Protocol { return v.Protocol } + // GetState returns ExternalConnectionPortInput.State, and is useful for accessing the field via an interface. func (v *ExternalConnectionPortInput) GetState() State { return v.State } @@ -1799,7 +1897,7 @@ type ExternalConnectionResultPortsExternalConnectionPort struct { AllowList []string `json:"allowList"` ExternalPort int `json:"externalPort"` InternalPort *int `json:"internalPort"` - Protocol string `json:"protocol"` + Protocol Protocol `json:"protocol"` } // GetAllowList returns ExternalConnectionResultPortsExternalConnectionPort.AllowList, and is useful for accessing the field via an interface. @@ -1818,7 +1916,9 @@ func (v *ExternalConnectionResultPortsExternalConnectionPort) GetInternalPort() } // GetProtocol returns ExternalConnectionResultPortsExternalConnectionPort.Protocol, and is useful for accessing the field via an interface. -func (v *ExternalConnectionResultPortsExternalConnectionPort) GetProtocol() string { return v.Protocol } +func (v *ExternalConnectionResultPortsExternalConnectionPort) GetProtocol() Protocol { + return v.Protocol +} type HealthCheckInput struct { Port int `json:"port"` @@ -2525,6 +2625,18 @@ type NamespaceResultVolumesVolume struct { // GetName returns NamespaceResultVolumesVolume.Name, and is useful for accessing the field via an interface. func (v *NamespaceResultVolumesVolume) GetName() string { return v.Name } +type Protocol string + +const ( + ProtocolTcp Protocol = "TCP" + ProtocolUdp Protocol = "UDP" +) + +var AllProtocol = []Protocol{ + ProtocolTcp, + ProtocolUdp, +} + type RegistryCreateInput struct { Namespace string `json:"namespace"` Name string `json:"name"` @@ -3501,6 +3613,11 @@ func (v *containerListNamespaceContainersContainer) GetEnvironmentVariables() [] return v.ContainerResult.EnvironmentVariables } +// GetExternalConnection returns containerListNamespaceContainersContainer.ExternalConnection, and is useful for accessing the field via an interface. +func (v *containerListNamespaceContainersContainer) GetExternalConnection() *ContainerResultExternalConnection { + return v.ContainerResult.ExternalConnection +} + // GetPorts returns containerListNamespaceContainersContainer.Ports, and is useful for accessing the field via an interface. func (v *containerListNamespaceContainersContainer) GetPorts() []string { return v.ContainerResult.Ports @@ -3587,6 +3704,8 @@ type __premarshalcontainerListNamespaceContainersContainer struct { EnvironmentVariables []EnvironmentVariableResult `json:"environmentVariables"` + ExternalConnection *ContainerResultExternalConnection `json:"externalConnection"` + Ports []string `json:"ports"` Ingresses []ContainerResultIngressesIngress `json:"ingresses"` @@ -3626,6 +3745,7 @@ func (v *containerListNamespaceContainersContainer) __premarshalJSON() (*__prema retval.Command = v.ContainerResult.Command retval.Entrypoint = v.ContainerResult.Entrypoint retval.EnvironmentVariables = v.ContainerResult.EnvironmentVariables + retval.ExternalConnection = v.ContainerResult.ExternalConnection retval.Ports = v.ContainerResult.Ports retval.Ingresses = v.ContainerResult.Ingresses retval.Mounts = v.ContainerResult.Mounts @@ -5120,6 +5240,9 @@ fragment ContainerResult on Container { environmentVariables { ... EnvironmentVariableResult } + externalConnection { + ... ExternalConnectionResult + } ports ingresses { domainName @@ -5156,6 +5279,16 @@ fragment EnvironmentVariableResult on EnvironmentVariable { value secret } +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } +} fragment ContainerMounts on Mount { path volume { @@ -5211,6 +5344,9 @@ fragment ContainerResult on Container { environmentVariables { ... EnvironmentVariableResult } + externalConnection { + ... ExternalConnectionResult + } ports ingresses { domainName @@ -5247,6 +5383,16 @@ fragment EnvironmentVariableResult on EnvironmentVariable { value secret } +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } +} fragment ContainerMounts on Mount { path volume { @@ -5650,6 +5796,9 @@ fragment ContainerResult on Container { environmentVariables { ... EnvironmentVariableResult } + externalConnection { + ... ExternalConnectionResult + } ports ingresses { domainName @@ -5686,6 +5835,16 @@ fragment EnvironmentVariableResult on EnvironmentVariable { value secret } +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } +} fragment ContainerMounts on Mount { path volume { @@ -5739,6 +5898,9 @@ fragment ContainerResult on Container { environmentVariables { ... EnvironmentVariableResult } + externalConnection { + ... ExternalConnectionResult + } ports ingresses { domainName @@ -5775,6 +5937,16 @@ fragment EnvironmentVariableResult on EnvironmentVariable { value secret } +fragment ExternalConnectionResult on ExternalConnection { + ipv4 + ipv6 + ports { + allowList + externalPort + internalPort + protocol + } +} fragment ContainerMounts on Mount { path volume { diff --git a/cmd/cloudDatabaseCluster.go b/cmd/cloudDatabaseCluster.go index 53d9db2..2e4cce5 100644 --- a/cmd/cloudDatabaseCluster.go +++ b/cmd/cloudDatabaseCluster.go @@ -227,7 +227,7 @@ func init() { cloudDatabaseClusterCmd.AddCommand(listCloudDatabaseClustersCmd) - deleteCloudDatabaseClusterCmd.Flags().String("namespace", "", "Namespace") + deleteCloudDatabaseClusterCmd.Flags().StringP("namespace", "n", "", "Namespace") deleteCloudDatabaseClusterCmd.Flags().String("name", "", "Name of the cluster") deleteCloudDatabaseClusterCmd.MarkFlagRequired("namespace") deleteCloudDatabaseClusterCmd.MarkFlagRequired("name") diff --git a/cmd/cloudDatabaseClusterDatabase.go b/cmd/cloudDatabaseClusterDatabase.go index 674f089..d916f32 100644 --- a/cmd/cloudDatabaseClusterDatabase.go +++ b/cmd/cloudDatabaseClusterDatabase.go @@ -105,13 +105,13 @@ var deleteCloudDatabaseClusterDatabaseCmd = &cobra.Command{ func init() { - listCloudDatabaseClusterDatabasesCmd.Flags().String("namespace", "", "Name of the namespace cluster belongs to") + listCloudDatabaseClusterDatabasesCmd.Flags().StringP("namespace", "n", "", "Namespace") listCloudDatabaseClusterDatabasesCmd.Flags().String("cluster", "", "Name of the cluster") listCloudDatabaseClusterDatabasesCmd.MarkFlagRequired("namespace") listCloudDatabaseClusterDatabasesCmd.MarkFlagRequired("cluster") cloudDatabaseClusterDatabaseCmd.AddCommand(listCloudDatabaseClusterDatabasesCmd) - deleteCloudDatabaseClusterDatabaseCmd.Flags().String("namespace", "", "Name of the namespace cluster belongs to") + deleteCloudDatabaseClusterDatabaseCmd.Flags().StringP("namespace", "n", "", "Namespace") deleteCloudDatabaseClusterDatabaseCmd.Flags().String("cluster", "", "Name of the cluster") deleteCloudDatabaseClusterDatabaseCmd.Flags().String("name", "", "Name of the database we want to delete") deleteCloudDatabaseClusterDatabaseCmd.MarkFlagRequired("namespace") @@ -119,7 +119,7 @@ func init() { deleteCloudDatabaseClusterDatabaseCmd.MarkFlagRequired("name") cloudDatabaseClusterDatabaseCmd.AddCommand(deleteCloudDatabaseClusterDatabaseCmd) - createCloudDatabaseClusterDatabaseCmd.Flags().String("namespace", "", "Name of namespace to create the cluster in") + createCloudDatabaseClusterDatabaseCmd.Flags().StringP("namespace", "n", "", "Namespace") createCloudDatabaseClusterDatabaseCmd.Flags().String("cluster", "", "Name of the cluster") createCloudDatabaseClusterDatabaseCmd.Flags().String("name", "", "Name of the database we want to create") createCloudDatabaseClusterDatabaseCmd.Flags().String("description", "", "Description of the database") diff --git a/cmd/cloudDatabaseClusterExternalConnection.go b/cmd/cloudDatabaseClusterExternalConnection.go index cc46044..7757cb9 100644 --- a/cmd/cloudDatabaseClusterExternalConnection.go +++ b/cmd/cloudDatabaseClusterExternalConnection.go @@ -82,14 +82,14 @@ var disableCloudDatabaseClusterExternalConnectionCmd = &cobra.Command{ } func init() { - enableCloudDatabaseClusterExternalConnectionCmd.Flags().String("namespace", "", "Namespace") + enableCloudDatabaseClusterExternalConnectionCmd.Flags().StringP("namespace", "n", "", "Namespace") enableCloudDatabaseClusterExternalConnectionCmd.Flags().String("cluster", "", "Name of the cluster") enableCloudDatabaseClusterExternalConnectionCmd.Flags().StringArray("allowed-ip", []string{"0.0.0.0/0", "::/0"}, "Allowed ip for the connection") enableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("namespace") enableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("cluster") cloudDatabaseClusterEnableExternalConnectionCmd.AddCommand(enableCloudDatabaseClusterExternalConnectionCmd) - disableCloudDatabaseClusterExternalConnectionCmd.Flags().String("namespace", "", "Namespace") + disableCloudDatabaseClusterExternalConnectionCmd.Flags().StringP("namespace", "n", "", "Namespace") disableCloudDatabaseClusterExternalConnectionCmd.Flags().String("cluster", "", "Name of the cluster") disableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("namespace") disableCloudDatabaseClusterExternalConnectionCmd.MarkFlagRequired("cluster") diff --git a/cmd/container.go b/cmd/container.go index 461133a..a5dc90c 100644 --- a/cmd/container.go +++ b/cmd/container.go @@ -17,7 +17,7 @@ var containerCmd = &cobra.Command{ var getContainerCmd = &cobra.Command{ Use: "get", - Short: "Get details of a container job", + Short: "Get details of a container", Run: func(cmd *cobra.Command, args []string) { namespace, _ := cmd.Flags().GetString("namespace") name, _ := cmd.Flags().GetString("name") @@ -251,7 +251,7 @@ var deleteContainerCmd = &cobra.Command{ } func init() { - createContainerCmd.Flags().String("namespace", "", "Namespace") + createContainerCmd.Flags().StringP("namespace", "n", "", "Namespace") createContainerCmd.Flags().String("name", "", "Name for the container") createContainerCmd.Flags().String("image", "", "Container image") createContainerCmd.Flags().String("resources", "", "Container resources") @@ -264,7 +264,7 @@ func init() { createContainerCmd.Flags().StringArray("command", []string{}, "Command to run in the container") containerCmd.AddCommand(createContainerCmd) - createStarterContainerCmd.Flags().String("namespace", "", "Namespace") + createStarterContainerCmd.Flags().StringP("namespace", "n", "", "Namespace") createStarterContainerCmd.Flags().String("name", "", "Name for the container") createStarterContainerCmd.Flags().String("image", "", "Container image") createStarterContainerCmd.Flags().StringArray("env", []string{}, "Container environment variables") @@ -276,7 +276,7 @@ func init() { createStarterContainerCmd.MarkFlagRequired("image") containerCmd.AddCommand(createStarterContainerCmd) - modifyContainerCmd.Flags().String("namespace", "", "Namespace") + modifyContainerCmd.Flags().StringP("namespace", "n", "", "Namespace") modifyContainerCmd.Flags().String("name", "", "Name for the container") modifyContainerCmd.Flags().String("image", "", "Container image") modifyContainerCmd.Flags().String("resources", "", "Container resources") @@ -291,19 +291,21 @@ func init() { modifyContainerCmd.MarkFlagRequired("name") containerCmd.AddCommand(modifyContainerCmd) - listContainersCmd.Flags().String("namespace", "", "Namespace") + listContainersCmd.Flags().StringP("namespace", "n", "", "Namespace") listContainersCmd.MarkFlagRequired("namespace") containerCmd.AddCommand(listContainersCmd) - deleteContainerCmd.Flags().String("namespace", "", "Namespace") + deleteContainerCmd.Flags().StringP("namespace", "n", "", "Namespace") deleteContainerCmd.Flags().String("name", "", "Name of this container") deleteContainerCmd.MarkFlagRequired("namespace") deleteContainerCmd.MarkFlagRequired("name") containerCmd.AddCommand(deleteContainerCmd) - getContainerCmd.Flags().String("namespace", "", "Namespace") + getContainerCmd.Flags().StringP("namespace", "n", "", "Namespace") getContainerCmd.Flags().String("name", "", "Name of the container") getContainerCmd.MarkFlagRequired("namespace") getContainerCmd.MarkFlagRequired("Name") - containerCmd.AddCommand(getContainerJobCmd) + containerCmd.AddCommand(getContainerCmd) + + containerCmd.AddCommand(containerEnableExternalConnectionCmd) } diff --git a/cmd/containerExternalConnection.go b/cmd/containerExternalConnection.go new file mode 100644 index 0000000..9c9d2c5 --- /dev/null +++ b/cmd/containerExternalConnection.go @@ -0,0 +1,208 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "strings" + "text/tabwriter" + + "github.com/nexaa-cloud/nexaa-cli/api" + "github.com/spf13/cobra" +) + +var containerEnableExternalConnectionCmd = &cobra.Command{ + Use: "external-connection", + Short: "Enable or Disable external connection on a cloud database cluster", +} + +var enableContainerExternalConnectionCmd = &cobra.Command{ + Use: "enable", + Short: "Enable external connection on a cloud database cluster", + Run: func(cmd *cobra.Command, args []string) { + namespace, _ := cmd.Flags().GetString("namespace") + containerName, _ := cmd.Flags().GetString("name") + allowedIp, _ := cmd.Flags().GetStringArray("allowed-ip") + internalPort, _ := cmd.Flags().GetInt("internal-port") + externalPort, _ := cmd.Flags().GetInt("external-port") + protocol, _ := cmd.Flags().GetString("protocol") + + if internalPort == 0 { + log.Fatalf("Internal port must be provided and cannot be 0") + return + } + + allowList := make([]api.AllowListInput, 0) + for _, ip := range allowedIp { + allowList = append(allowList, api.AllowListInput{Ip: ip, State: api.StatePresent}) + } + + resource := api.ContainerModifyInput{} + + if externalPort == 0 { + resource = api.ContainerModifyInput{ + Name: containerName, + Namespace: namespace, + ExternalConnection: &api.ExternalConnectionInput{ + State: api.StatePresent, + SharedIp: true, + Ports: []api.ExternalConnectionPortInput{ + { + AllowList: allowList, + State: api.StatePresent, + InternalPort: &internalPort, + Protocol: api.Protocol(protocol), + }, + }, + }, + } + } else { + resource = api.ContainerModifyInput{ + Name: containerName, + Namespace: namespace, + ExternalConnection: &api.ExternalConnectionInput{ + State: api.StatePresent, + SharedIp: true, + Ports: []api.ExternalConnectionPortInput{ + { + AllowList: allowList, + State: api.StatePresent, + InternalPort: &internalPort, + ExternalPort: &externalPort, + Protocol: api.Protocol(protocol), + }, + }, + }, + } + } + + client := api.NewClient() + + cluster, err := client.ContainerModify(resource) + if err != nil { + log.Fatalf("Failed to enable external connection in cluster %q/%q: %v", namespace, containerName, err) + return + } + fmt.Printf("External connection enabled. Reachable at:\n") + fmt.Printf("Ipv4: %s:%d \n", cluster.ExternalConnection.Ipv4, cluster.ExternalConnection.Ports[0].ExternalPort) + fmt.Printf("Ipv6: %s:%d \n", cluster.ExternalConnection.Ipv6, cluster.ExternalConnection.Ports[0].ExternalPort) + }, +} + +var disableContainerExternalConnectionCmd = &cobra.Command{ + Use: "disable", + Short: "disable external connection on a container", + Run: func(cmd *cobra.Command, args []string) { + namespace, _ := cmd.Flags().GetString("namespace") + name, _ := cmd.Flags().GetString("name") + externalPort, _ := cmd.Flags().GetInt("external-port") + + client := api.NewClient() + + // If externalPort is not provided, it will disable all external connections on the container + resource := api.ContainerModifyInput{} + if externalPort == 0 { + resource = api.ContainerModifyInput{ + Name: name, + Namespace: namespace, + ExternalConnection: &api.ExternalConnectionInput{ + State: api.StateAbsent, + Ports: []api.ExternalConnectionPortInput{}, + }, + } + } else { + ports := []api.ExternalConnectionPortInput{} + + ports = append(ports, api.ExternalConnectionPortInput{ + ExternalPort: &externalPort, + State: api.StateAbsent, + Protocol: api.ProtocolUdp, + AllowList: []api.AllowListInput{}, + }) + + resource = api.ContainerModifyInput{ + Name: name, + Namespace: namespace, + ExternalConnection: &api.ExternalConnectionInput{ + State: api.StatePresent, + Ports: ports, + }, + } + } + + _, err := client.ContainerModify(resource) + + if err != nil { + log.Fatalf("Failed to disable external connection in cluster %q/%q: %v", namespace, name, err) + return + } + fmt.Printf("External connection disabled in: %s/%s. \n", namespaceCmd, name) + }, +} + +var listContainerExternalConnectionCmd = &cobra.Command{ + Use: "list", + Short: "list external connection on a cloud database cluster", + Run: func(cmd *cobra.Command, args []string) { + namespace, _ := cmd.Flags().GetString("namespace") + name, _ := cmd.Flags().GetString("name") + + client := api.NewClient() + container, err := client.ListContainerByName(namespace, name) + + if err != nil { + log.Fatalf("Container %q/%q not found: %v", namespace, name, err) + return + } + + if container.ExternalConnection == nil || len(container.ExternalConnection.Ports) == 0 { + log.Printf("No external connections enabled on %q/%q.", namespace, name) + return + } + + writer := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.Debug) + fmt.Fprintln(writer, "IPV4\t IPV6\t EXTERNAL PORT\t INTERNAL PORT\t PROTOCOL\t ALLOWLIST\t") + + for _, port := range container.ExternalConnection.Ports { + fmt.Fprintf( + writer, + "%s\t %s\t %d\t %d\t %s\t %s\n", + container.ExternalConnection.Ipv4, + container.ExternalConnection.Ipv4, + port.ExternalPort, + *port.InternalPort, + port.Protocol, + strings.Join(port.AllowList, ","), + ) + } + + writer.Flush() + }, +} + +func init() { + enableContainerExternalConnectionCmd.Flags().StringP("namespace", "n", "", "Namespace") + enableContainerExternalConnectionCmd.Flags().String("name", "", "Name of the container") + enableContainerExternalConnectionCmd.Flags().StringArray("allowed-ip", []string{"0.0.0.0/0", "::/0"}, "Allowed ip for the connection") + enableContainerExternalConnectionCmd.Flags().Int("internal-port", 0, "Internal port to enable external connection on") + enableContainerExternalConnectionCmd.Flags().Int("external-port", 0, "Internal port to enable external connection on") + enableContainerExternalConnectionCmd.Flags().String("protocol", "TCP", "Protocol for the external connection (tcp or udp)") + enableContainerExternalConnectionCmd.MarkFlagRequired("namespace") + enableContainerExternalConnectionCmd.MarkFlagRequired("name") + enableContainerExternalConnectionCmd.MarkFlagRequired("internal-port") + containerEnableExternalConnectionCmd.AddCommand(enableContainerExternalConnectionCmd) + + disableContainerExternalConnectionCmd.Flags().StringP("namespace", "n", "", "Namespace") + disableContainerExternalConnectionCmd.Flags().String("name", "", "Name of the container") + disableContainerExternalConnectionCmd.Flags().Int("external-port", 0, "External port to disable") + disableContainerExternalConnectionCmd.MarkFlagRequired("namespace") + disableContainerExternalConnectionCmd.MarkFlagRequired("name") + containerEnableExternalConnectionCmd.AddCommand(disableContainerExternalConnectionCmd) + + listContainerExternalConnectionCmd.Flags().StringP("namespace", "n", "", "Namespace") + listContainerExternalConnectionCmd.Flags().String("name", "", "Name of the container") + listContainerExternalConnectionCmd.MarkFlagRequired("namespace") + listContainerExternalConnectionCmd.MarkFlagRequired("name") + containerEnableExternalConnectionCmd.AddCommand(listContainerExternalConnectionCmd) + +} diff --git a/cmd/container_job.go b/cmd/container_job.go index 7200c71..e8b5c5b 100644 --- a/cmd/container_job.go +++ b/cmd/container_job.go @@ -205,7 +205,7 @@ var deleteContainerJobCmd = &cobra.Command{ } func init() { - createContainerJobCmd.Flags().String("namespace", "", "Namespace") + createContainerJobCmd.Flags().StringP("namespace", "n", "", "Namespace") createContainerJobCmd.Flags().String("name", "", "Name for this container job") createContainerJobCmd.Flags().String("image", "", "Container job image") createContainerJobCmd.Flags().String("resources", "", "Container job resources") @@ -222,7 +222,7 @@ func init() { createContainerJobCmd.MarkFlagRequired("schedule") containerJobCmd.AddCommand(createContainerJobCmd) - modifyContainerJobCmd.Flags().String("namespace", "", "Namespace") + modifyContainerJobCmd.Flags().StringP("namespace", "n", "", "Namespace") modifyContainerJobCmd.Flags().String("name", "", "Name for this container job") modifyContainerJobCmd.Flags().String("image", "", "Container job image") modifyContainerJobCmd.Flags().String("resources", "", "Container job resources") @@ -237,17 +237,17 @@ func init() { modifyContainerJobCmd.MarkFlagRequired("name") containerJobCmd.AddCommand(modifyContainerJobCmd) - listContainerJobsCmd.Flags().String("namespace", "", "Namespace") + listContainerJobsCmd.Flags().StringP("namespace", "n", "", "Namespace") listContainerJobsCmd.MarkFlagRequired("namespace") containerJobCmd.AddCommand(listContainerJobsCmd) - deleteContainerJobCmd.Flags().String("namespace", "", "Namespace") + deleteContainerJobCmd.Flags().StringP("namespace", "n", "", "Namespace") deleteContainerJobCmd.Flags().String("name", "", "Name of the containerjob") deleteContainerJobCmd.MarkFlagRequired("namespace") deleteContainerJobCmd.MarkFlagRequired("name") containerJobCmd.AddCommand(deleteContainerJobCmd) - getContainerJobCmd.Flags().String("namespace", "", "Namespace") + getContainerJobCmd.Flags().StringP("namespace", "n", "", "Namespace") getContainerJobCmd.Flags().String("name", "", "Name of the container job") getContainerJobCmd.MarkFlagRequired("namespace") getContainerJobCmd.MarkFlagRequired("Name") diff --git a/cmd/messageQueue.go b/cmd/messageQueue.go index 886cd49..f9167c3 100644 --- a/cmd/messageQueue.go +++ b/cmd/messageQueue.go @@ -234,14 +234,14 @@ func init() { messageQueueCmd.AddCommand(listMessageQueuesCmd) // Get command - getMessageQueueCmd.Flags().String("namespace", "", "Namespace") + getMessageQueueCmd.Flags().StringP("namespace", "n", "", "Namespace") getMessageQueueCmd.Flags().String("name", "", "Name of the message queue") getMessageQueueCmd.MarkFlagRequired("namespace") getMessageQueueCmd.MarkFlagRequired("name") messageQueueCmd.AddCommand(getMessageQueueCmd) // Create command - createMessageQueueCmd.Flags().String("namespace", "", "Namespace") + createMessageQueueCmd.Flags().StringP("namespace", "n", "", "Namespace") createMessageQueueCmd.Flags().String("name", "", "Name for the message queue") createMessageQueueCmd.Flags().String("plan", "", "Plan ID for the message queue") createMessageQueueCmd.Flags().String("type", "", "Type of the message queue (e.g., RabbitMQ)") @@ -255,7 +255,7 @@ func init() { messageQueueCmd.AddCommand(createMessageQueueCmd) // Delete command - deleteMessageQueueCmd.Flags().String("namespace", "", "Namespace") + deleteMessageQueueCmd.Flags().StringP("namespace", "n", "", "Namespace") deleteMessageQueueCmd.Flags().String("name", "", "Name of the message queue") deleteMessageQueueCmd.MarkFlagRequired("namespace") deleteMessageQueueCmd.MarkFlagRequired("name") @@ -268,7 +268,7 @@ func init() { messageQueueCmd.AddCommand(listMessageQueueVersionsCmd) // Admin credentials command - listAdminUserCredentialsCmd.Flags().String("namespace", "", "Namespace name") + listAdminUserCredentialsCmd.Flags().StringP("namespace", "n", "", "Namespace") listAdminUserCredentialsCmd.Flags().String("name", "", "Name of the message queue") listAdminUserCredentialsCmd.Flags().String("username", "", "Admin username") listAdminUserCredentialsCmd.MarkFlagRequired("namespace") diff --git a/cmd/messageQueueExternalConnection.go b/cmd/messageQueueExternalConnection.go index d9aa5d1..06af6f5 100644 --- a/cmd/messageQueueExternalConnection.go +++ b/cmd/messageQueueExternalConnection.go @@ -82,14 +82,14 @@ var disableMessageQueueExternalConnectionCmd = &cobra.Command{ } func init() { - enableMessageQueueExternalConnectionCmd.Flags().String("namespace", "", "Namespace") + enableMessageQueueExternalConnectionCmd.Flags().StringP("namespace", "n", "", "Namespace") enableMessageQueueExternalConnectionCmd.Flags().String("cluster", "", "Name of the cluster") enableMessageQueueExternalConnectionCmd.Flags().StringArray("allowed-ip", []string{"0.0.0.0/0", "::/0"}, "Allowed ip for the connection") enableMessageQueueExternalConnectionCmd.MarkFlagRequired("namespace") enableMessageQueueExternalConnectionCmd.MarkFlagRequired("cluster") messageQueueEnableExternalConnectionCmd.AddCommand(enableMessageQueueExternalConnectionCmd) - disableMessageQueueExternalConnectionCmd.Flags().String("namespace", "", "Namespace") + disableMessageQueueExternalConnectionCmd.Flags().StringP("namespace", "n", "", "Namespace") disableMessageQueueExternalConnectionCmd.Flags().String("cluster", "", "Name of the cluster") disableMessageQueueExternalConnectionCmd.MarkFlagRequired("namespace") disableMessageQueueExternalConnectionCmd.MarkFlagRequired("cluster") diff --git a/cmd/registry.go b/cmd/registry.go index f3c24f6..f0ae7f0 100644 --- a/cmd/registry.go +++ b/cmd/registry.go @@ -100,11 +100,11 @@ var deleteRegistryCmd = &cobra.Command{ } func init() { - listRegistriesCmd.Flags().StringP("namespace", "", "", "Namespace") + listRegistriesCmd.Flags().StringP("namespace", "n", "", "Namespace") listRegistriesCmd.MarkFlagRequired("namespace") registryCmd.AddCommand(listRegistriesCmd) - createRegistryCmd.Flags().String("namespace", "", "Namespace") + createRegistryCmd.Flags().StringP("namespace", "n", "", "Namespace") createRegistryCmd.Flags().String("name", "", "Name for the private registry") createRegistryCmd.Flags().String("source", "", "Source URL for the private registry") createRegistryCmd.Flags().String("username", "", "Username for the private registry") @@ -116,7 +116,7 @@ func init() { createRegistryCmd.MarkFlagRequired("password") registryCmd.AddCommand(createRegistryCmd) - deleteRegistryCmd.Flags().String("namespace", "", "Namespace") + deleteRegistryCmd.Flags().StringP("namespace", "n", "", "Namespace") deleteRegistryCmd.Flags().String("name", "", "Name of the private registry") deleteRegistryCmd.MarkFlagRequired("namespace") deleteRegistryCmd.MarkFlagRequired("name") diff --git a/cmd/volume.go b/cmd/volume.go index 8e7b8cb..f2e082a 100644 --- a/cmd/volume.go +++ b/cmd/volume.go @@ -120,11 +120,11 @@ var deleteVolumeCmd = &cobra.Command{ } func init() { - listVolumesCmd.Flags().String("namespace", "", "Namespace") + listVolumesCmd.Flags().StringP("namespace", "n", "", "Namespace") listVolumesCmd.MarkFlagRequired("namespace") volumeCmd.AddCommand(listVolumesCmd) - createVolumeCmd.Flags().String("namespace", "", "Namespace") + createVolumeCmd.Flags().StringP("namespace", "n", "", "Namespace") createVolumeCmd.Flags().String("name", "", "Name for the volume") createVolumeCmd.Flags().Int("size", 0, "Size of the volume") createVolumeCmd.MarkFlagRequired("namespace") @@ -132,7 +132,7 @@ func init() { createVolumeCmd.MarkFlagRequired("size") volumeCmd.AddCommand(createVolumeCmd) - increaseVolumeCmd.Flags().String("namespace", "", "Namespace") + increaseVolumeCmd.Flags().StringP("namespace", "n", "", "Namespace") increaseVolumeCmd.Flags().String("name", "", "Name of the volume") increaseVolumeCmd.Flags().Int("size", 0, "Size of the volume") increaseVolumeCmd.MarkFlagRequired("namespace") @@ -140,7 +140,7 @@ func init() { increaseVolumeCmd.MarkFlagRequired("size") volumeCmd.AddCommand(increaseVolumeCmd) - deleteVolumeCmd.Flags().String("namespace", "", "Namespace") + deleteVolumeCmd.Flags().StringP("namespace", "n", "", "Namespace") deleteVolumeCmd.Flags().String("name", "", "name") deleteVolumeCmd.MarkFlagRequired("namespace") deleteVolumeCmd.MarkFlagRequired("name") diff --git a/operations/Container.graphql b/operations/Container.graphql index 04a14f2..928f095 100644 --- a/operations/Container.graphql +++ b/operations/Container.graphql @@ -11,6 +11,9 @@ fragment ContainerResult on Container { environmentVariables { ... EnvironmentVariableResult } + externalConnection { + ... ExternalConnectionResult + } ports ingresses { domainName diff --git a/schema.graphql b/schema.graphql index 9dd2166..f03dfdc 100644 --- a/schema.graphql +++ b/schema.graphql @@ -294,9 +294,9 @@ type Container { containerType: ContainerType! @deprecated(reason: "use `type` instead") createdAt: DateTime! deletedAt: DateTime @deprecated(reason: "this field will be removed in the future, and doesn't have a replacement") - endpoints: [ContainerEndpoint!]! entrypoint: [String!] environmentVariables: [EnvironmentVariable!]! + externalConnection: ExternalConnection healthCheck: HealthCheck id: ID! image: String! @@ -388,6 +388,9 @@ input ContainerCreateInput { This field is defined in docker exec format. https://docs.docker.com/reference/dockerfile/#shell-and-exec-form """ command: [String!] + + "" + externalConnection: ExternalConnectionInput = null } input ContainerDeleteInput { @@ -634,6 +637,9 @@ input ContainerModifyInput { This field is defined in docker exec format. https://docs.docker.com/reference/dockerfile/#shell-and-exec-form """ command: [String!] + + "" + externalConnection: ExternalConnectionInput } input ContainerResourceInput { @@ -926,13 +932,27 @@ type ExternalConnectionPort { allowList: [String!]! externalPort: Int! internalPort: Int - protocol: String! + protocol: Protocol! } input ExternalConnectionPortInput { "" externalPort: Int = null + """ + Internal port is only used for external connections of containers. + For other resources, it's automatically selected + + See ports in `ContainerCreateInput` and `ContainerModifyInput` + """ + internalPort: Int = null + + """ + Protocol is only used for external connections of containers. + For other resources, only TCP is allowed. + """ + protocol: Protocol! = TCP + "" state: State! = PRESENT @@ -1402,6 +1422,11 @@ type PrivateRegistry { username: String! } +enum Protocol { + TCP + UDP +} + type Query { """ Returns the current user account. @@ -1705,3 +1730,4 @@ input VolumeResourceInput { "" namespace: String! } + From 5d6a8bc66fcf2c5a8a2625bc1dce71fa944ecd48 Mon Sep 17 00:00:00 2001 From: Jaap van Otterdijk Date: Thu, 12 Mar 2026 16:16:38 +0100 Subject: [PATCH 10/13] Allow adding external connections to container --- cmd/containerExternalConnection.go | 50 +++++++++++++++++------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/cmd/containerExternalConnection.go b/cmd/containerExternalConnection.go index 9c9d2c5..ada0a70 100644 --- a/cmd/containerExternalConnection.go +++ b/cmd/containerExternalConnection.go @@ -78,14 +78,14 @@ var enableContainerExternalConnectionCmd = &cobra.Command{ client := api.NewClient() - cluster, err := client.ContainerModify(resource) + container, err := client.ContainerModify(resource) if err != nil { log.Fatalf("Failed to enable external connection in cluster %q/%q: %v", namespace, containerName, err) return } - fmt.Printf("External connection enabled. Reachable at:\n") - fmt.Printf("Ipv4: %s:%d \n", cluster.ExternalConnection.Ipv4, cluster.ExternalConnection.Ports[0].ExternalPort) - fmt.Printf("Ipv6: %s:%d \n", cluster.ExternalConnection.Ipv6, cluster.ExternalConnection.Ports[0].ExternalPort) + + fmt.Printf("External connection enabled.\n") + printConnections(container) }, } @@ -130,13 +130,15 @@ var disableContainerExternalConnectionCmd = &cobra.Command{ } } - _, err := client.ContainerModify(resource) + container, err := client.ContainerModify(resource) if err != nil { log.Fatalf("Failed to disable external connection in cluster %q/%q: %v", namespace, name, err) return } fmt.Printf("External connection disabled in: %s/%s. \n", namespaceCmd, name) + + printConnections(container) }, } @@ -160,26 +162,30 @@ var listContainerExternalConnectionCmd = &cobra.Command{ return } - writer := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.Debug) - fmt.Fprintln(writer, "IPV4\t IPV6\t EXTERNAL PORT\t INTERNAL PORT\t PROTOCOL\t ALLOWLIST\t") - - for _, port := range container.ExternalConnection.Ports { - fmt.Fprintf( - writer, - "%s\t %s\t %d\t %d\t %s\t %s\n", - container.ExternalConnection.Ipv4, - container.ExternalConnection.Ipv4, - port.ExternalPort, - *port.InternalPort, - port.Protocol, - strings.Join(port.AllowList, ","), - ) - } - - writer.Flush() + printConnections(container) }, } +func printConnections(container api.ContainerResult) { + writer := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.Debug) + fmt.Fprintln(writer, "IPV4\t IPV6\t EXTERNAL PORT\t INTERNAL PORT\t PROTOCOL\t ALLOWLIST\t") + + for _, port := range container.ExternalConnection.Ports { + fmt.Fprintf( + writer, + "%s\t %s\t %d\t %d\t %s\t %s\n", + container.ExternalConnection.Ipv4, + container.ExternalConnection.Ipv4, + port.ExternalPort, + *port.InternalPort, + port.Protocol, + strings.Join(port.AllowList, ","), + ) + } + + writer.Flush() +} + func init() { enableContainerExternalConnectionCmd.Flags().StringP("namespace", "n", "", "Namespace") enableContainerExternalConnectionCmd.Flags().String("name", "", "Name of the container") From a270892b17fe4df9df85442436049041d1ba655c Mon Sep 17 00:00:00 2001 From: Matthijs van Gastel Date: Mon, 16 Mar 2026 11:59:42 +0100 Subject: [PATCH 11/13] Fixed typo in the response of disable external connection for container --- cmd/containerExternalConnection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/containerExternalConnection.go b/cmd/containerExternalConnection.go index ada0a70..f054298 100644 --- a/cmd/containerExternalConnection.go +++ b/cmd/containerExternalConnection.go @@ -136,7 +136,7 @@ var disableContainerExternalConnectionCmd = &cobra.Command{ log.Fatalf("Failed to disable external connection in cluster %q/%q: %v", namespace, name, err) return } - fmt.Printf("External connection disabled in: %s/%s. \n", namespaceCmd, name) + fmt.Printf("External connection disabled in: %s/%s. \n", namespace, name) printConnections(container) }, From 0abe58cad4d101cd76a47a6f9fb55359ff6d81b9 Mon Sep 17 00:00:00 2001 From: Matthijs van Gastel Date: Mon, 16 Mar 2026 12:05:44 +0100 Subject: [PATCH 12/13] Fixed ineffectual assignment --- cmd/containerExternalConnection.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/containerExternalConnection.go b/cmd/containerExternalConnection.go index f054298..0ca18e0 100644 --- a/cmd/containerExternalConnection.go +++ b/cmd/containerExternalConnection.go @@ -37,7 +37,7 @@ var enableContainerExternalConnectionCmd = &cobra.Command{ allowList = append(allowList, api.AllowListInput{Ip: ip, State: api.StatePresent}) } - resource := api.ContainerModifyInput{} + var resource api.ContainerModifyInput if externalPort == 0 { resource = api.ContainerModifyInput{ @@ -100,7 +100,7 @@ var disableContainerExternalConnectionCmd = &cobra.Command{ client := api.NewClient() // If externalPort is not provided, it will disable all external connections on the container - resource := api.ContainerModifyInput{} + var resource api.ContainerModifyInput if externalPort == 0 { resource = api.ContainerModifyInput{ Name: name, From 483f953eed0b9289498c3579715499e82dcbdb5d Mon Sep 17 00:00:00 2001 From: Matthijs van Gastel Date: Mon, 16 Mar 2026 12:07:09 +0100 Subject: [PATCH 13/13] Updated the go.sum --- go.sum | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/go.sum b/go.sum index ec00107..e79d515 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,7 @@ github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= -github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= -github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= -github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= -github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= -github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= -github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= -github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -50,22 +42,15 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k= github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=