Repository: superfly/flyctl
Pull request #3173 (2024-01-10 10:32:28)

Update help message for scale vm
### Change Summary

What and Why:

Update help message to reflect actual vm names.

How:

Related to:

---

### Documentation

- [ ] Fresh Produce
- [ ] In superfly/docs, or asked for help from docs team
- [x] n/a



Patch:
diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml
index c3242cd84..552099f43 100644
--- a/.github/workflows/ci-dev.yml
+++ b/.github/workflows/ci-dev.yml
@@ -21,7 +21,7 @@ jobs:
 
   # create a dev tag for every branch except master
   tag_version:
-    if: ${{ github.ref_type == 'branch' && github.ref_name != 'master' }}
+    if: ${{ github.ref_type == 'branch' && github.ref_name != 'master' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' }}
     runs-on: ubuntu-latest
     outputs:
       version: ${{ steps.version.outputs.next }}
diff --git a/api/go.mod b/api/go.mod
index cc94505be..01ae7d7ea 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/superfly/graphql v0.2.4
 	go.opentelemetry.io/otel v1.21.0
 	go.opentelemetry.io/otel/trace v1.21.0
-	golang.org/x/crypto v0.17.0
+	golang.org/x/crypto v0.18.0
 )
 
 require (
@@ -18,5 +18,5 @@ require (
 	github.com/vektah/gqlparser/v2 v2.5.1 // indirect
 	go.opentelemetry.io/otel/metric v1.21.0 // indirect
 	golang.org/x/net v0.17.0 // indirect
-	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/sys v0.16.0 // indirect
 )
diff --git a/api/go.sum b/api/go.sum
index de5ed0406..e004ccd65 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -43,17 +43,17 @@ go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ3
 go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
 go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
 go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
+golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
 golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
+golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/go.mod b/go.mod
index a1fc965cf..07a4e7ff2 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,7 @@ require (
 	github.com/docker/docker v24.0.7+incompatible
 	github.com/dustin/go-humanize v1.0.1
 	github.com/ejcx/sshcert v1.1.0
-	github.com/getsentry/sentry-go v0.25.0
+	github.com/getsentry/sentry-go v0.26.0
 	github.com/gofrs/flock v0.8.1
 	github.com/google/go-cmp v0.6.0
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
@@ -67,12 +67,12 @@ require (
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
 	go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0
-	golang.org/x/crypto v0.17.0
+	golang.org/x/crypto v0.18.0
 	golang.org/x/exp v0.0.0-20230905200255-921286631fa9
-	golang.org/x/net v0.19.0
+	golang.org/x/net v0.20.0
 	golang.org/x/sync v0.6.0
 	golang.org/x/term v0.16.0
-	golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
+	golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
 	google.golang.org/grpc v1.60.1
 	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v3 v3.0.1
@@ -177,7 +177,7 @@ require (
 	google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
-	gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f // indirect
+	gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
 )
 
 require (
diff --git a/go.sum b/go.sum
index eccf0b896..e041c27dc 100644
--- a/go.sum
+++ b/go.sum
@@ -268,8 +268,8 @@ github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo
 github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04=
 github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
 github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
-github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
-github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
+github.com/getsentry/sentry-go v0.26.0 h1:IX3++sF6/4B5JcevhdZfdKIHfyvMmAq/UnqcyT2H6mA=
+github.com/getsentry/sentry-go v0.26.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
 github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
 github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
 github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@@ -654,8 +654,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw
 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
+golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
 golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
@@ -689,8 +689,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
-golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
-golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
 golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
@@ -776,8 +776,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
-golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
-golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
+golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
+golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
@@ -824,8 +824,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
 gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
-gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f h1:8GE2MRjGiFmfpon8dekPI08jEuNMQzSffVHgdupcO4E=
-gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
+gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
+gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
diff --git a/internal/appconfig/validation.go b/internal/appconfig/validation.go
index 5384a28e6..baac601a9 100644
--- a/internal/appconfig/validation.go
+++ b/internal/appconfig/validation.go
@@ -233,6 +233,14 @@ func (cfg *Config) validateServicesSection() (extraInfo string, err error) {
 			}
 		}
 
+		if len(service.Ports) == 0 {
+			extraInfo += fmt.Sprintf(
+				"Service must expose at least one port. Add a [[services.ports]] section to fly.toml; " +
+					"Check docs at https://fly.io/docs/reference/configuration/#services-ports \n",
+			)
+			err = ValidationError
+		}
+
 		for _, check := range service.TCPChecks {
 			extraInfo += validateServiceCheckDurations(check.Interval, check.Timeout, check.GracePeriod, "TCP")
 		}
diff --git a/internal/command/extensions/tigris/create.go b/internal/command/extensions/tigris/create.go
index 01d944b24..26371b184 100644
--- a/internal/command/extensions/tigris/create.go
+++ b/internal/command/extensions/tigris/create.go
@@ -32,6 +32,11 @@ func create() (cmd *cobra.Command) {
 			Shorthand:   "n",
 			Description: "The name of your bucket",
 		},
+		flag.Bool{
+			Name:        "public",
+			Shorthand:   "p",
+			Description: "Objects in the bucket should be publicly accessible",
+		},
 	)
 	return cmd
 }
@@ -52,6 +57,11 @@ func runCreate(ctx context.Context) (err error) {
 		params.Organization = org
 	}
 
+	if flag.GetBool(ctx, "public") {
+		params.Options = gql.AddOnOptions{
+			"public": true,
+		}
+	}
 	params.Provider = "tigris"
 	extension, err := extensions_core.ProvisionExtension(ctx, params)
 
diff --git a/internal/command/extensions/tigris/dashboard.go b/internal/command/extensions/tigris/dashboard.go
index 22ab03841..5149f3fc7 100644
--- a/internal/command/extensions/tigris/dashboard.go
+++ b/internal/command/extensions/tigris/dashboard.go
@@ -24,6 +24,7 @@ func dashboard() (cmd *cobra.Command) {
 	flag.Add(cmd,
 		flag.App(),
 		flag.AppConfig(),
+		flag.Org(),
 		extensions_core.SharedFlags,
 	)
 	cmd.Args = cobra.MaximumNArgs(1)
@@ -32,6 +33,12 @@ func dashboard() (cmd *cobra.Command) {
 
 func runDashboard(ctx context.Context) (err error) {
 
+	org := flag.GetOrg(ctx)
+
+	if org != "" {
+		return extensions_core.OpenOrgDashboard(ctx, org, "tigris")
+	}
+
 	extension, _, err := extensions_core.Discover(ctx, gql.AddOnTypeTigris)
 
 	if err != nil {
diff --git a/internal/command/scale/vm.go b/internal/command/scale/vm.go
index 5a919ac9c..4366da0df 100644
--- a/internal/command/scale/vm.go
+++ b/internal/command/scale/vm.go
@@ -15,7 +15,7 @@ import (
 
 func newScaleVm() *cobra.Command {
 	const (
-		short = "Change an app's VM to a named size (eg. shared-cpu-1x, dedicated-cpu-1x, dedicated-cpu-2x...)"
+		short = "Change an app's VM to a named size (eg. shared-cpu-1x, performance-1x, performance-2x...)"
 		long  = `Change an application's VM size to one of the named VM sizes.
 
 For a full list of supported sizes use the command 'flyctl platform vm-sizes'


Test patch:
diff --git a/internal/appconfig/testdata/validategroups.toml b/internal/appconfig/testdata/validate-groups.toml
similarity index 88%
rename from internal/appconfig/testdata/validategroups.toml
rename to internal/appconfig/testdata/validate-groups.toml
index a78c086bb..93daac191 100644
--- a/internal/appconfig/testdata/validategroups.toml
+++ b/internal/appconfig/testdata/validate-groups.toml
@@ -15,11 +15,17 @@ internal_port = 9999
 protocol = "udp"
 processes = ["vpn"]
 
+[[services.ports]]
+port = 80
+
 [[services]]
 internal_port = 1111
 protocol = "tcp"
 processes = ["vpn", "app", "foo"]
 
+[[services.ports]]
+port = 81
+
 [checks.listening]
 port = 8080
 type = "tcp"
diff --git a/internal/appconfig/testdata/validate-services.toml b/internal/appconfig/testdata/validate-services.toml
new file mode 100644
index 000000000..475c22d1e
--- /dev/null
+++ b/internal/appconfig/testdata/validate-services.toml
@@ -0,0 +1,20 @@
+app = "foo"
+primary_region = "ord"
+
+[processes]
+app = ""
+foo = ""
+success = ""
+
+# Check:
+# * missing [[services.ports]]
+# * no processes specified
+[[services]]
+internal_port = 8080
+
+[[services]]
+internal_port = 8080
+processes = ["success"]
+
+[[services.ports]]
+port = 80
diff --git a/internal/appconfig/validation_test.go b/internal/appconfig/validation_test.go
index 5e7636a9d..90cf44a4f 100644
--- a/internal/appconfig/validation_test.go
+++ b/internal/appconfig/validation_test.go
@@ -25,7 +25,7 @@ func _getValidationContext(t *testing.T) context.Context {
 }
 
 func TestConfig_ValidateGroups(t *testing.T) {
-	cfg, err := LoadConfig("./testdata/validategroups.toml")
+	cfg, err := LoadConfig("./testdata/validate-groups.toml")
 	require.NoError(t, err)
 	require.NoError(t, cfg.SetMachinesPlatform())
 	cfg.Deploy = &Deploy{Strategy: "canary"}
@@ -38,8 +38,8 @@ func TestConfig_ValidateGroups(t *testing.T) {
 	err, x = cfg.ValidateGroups(ctx, []string{"app"})
 	require.Error(t, err, x)
 
-	err, _ = cfg.ValidateGroups(ctx, []string{"foo"})
-	require.NoError(t, err)
+	err, x = cfg.ValidateGroups(ctx, []string{"foo"})
+	require.NoErrorf(t, err, x)
 }
 
 func TestConfig_ValidateMounts(t *testing.T) {
@@ -56,3 +56,18 @@ func TestConfig_ValidateMounts(t *testing.T) {
 	require.Error(t, err, x)
 	require.Contains(t, x, "group 'app' has more than one [[mounts]] section defined")
 }
+
+func TestConfig_ValidateServices(t *testing.T) {
+	cfg, err := LoadConfig("./testdata/validate-services.toml")
+	require.NoError(t, err)
+	require.NoError(t, cfg.SetMachinesPlatform())
+
+	ctx := _getValidationContext(t)
+	err, x := cfg.Validate(ctx)
+	require.Error(t, err, x)
+	require.Contains(t, x, "Service has no processes set")
+	require.Contains(t, x, "Service must expose at least one port")
+
+	err, x = cfg.ValidateGroups(ctx, []string{"success"})
+	require.NoErrorf(t, err, x)
+}
diff --git a/test/preflight/apps_v2_integration_test.go b/test/preflight/apps_v2_integration_test.go
index 3fc04d595..c19dcb259 100644
--- a/test/preflight/apps_v2_integration_test.go
+++ b/test/preflight/apps_v2_integration_test.go
@@ -255,6 +255,9 @@ func TestAppsV2Config_ProcessGroups(t *testing.T) {
   internal_port = 8080
   protocol = "tcp"
   script_checks = []
+
+		[[services.ports]]
+		port = 80
 `)
 	require.Contains(t, deployOut.StdOutString(), `create 1 "app" machine`)
 
@@ -279,6 +282,9 @@ bar_web = "bash -c 'while true; do sleep 10; done'"
   internal_port = 8080
   protocol = "tcp"
   script_checks = []
+
+		[[services.ports]]
+		port = 80
 `)
 	require.Contains(t, deployOut.StdOutString(), `destroy 1 "app" machine`)
 	require.Contains(t, deployOut.StdOutString(), `create 1 "web" machine`)
@@ -329,6 +335,9 @@ web = "nginx -g 'daemon off;'"
   internal_port = 8080
   protocol = "tcp"
   script_checks = []
+
+		[[services.ports]]
+		port = 80
 `)
 	require.Contains(t, deployOut.StdOutString(), `destroy 2 "bar_web" machines`)
 	machines = f.MachinesList(appName)