Skip to content

Conversation

@joergdw
Copy link
Contributor

@joergdw joergdw commented Nov 1, 2025

This contains the "app-autoscaler"-changes for cloudfoundry/app-autoscaler-release#3652.

Current top-commit-id of that branch: "9ca68503e2f08bd8123df39b5722decb0fcde1bb"

This PR comes with the following changes:

  • Service-key-support
    • Introduce the necessary handling into the Bind-function of the broker.
    • Unit-tests
    • Divide the schema into three logical units for three different scenarios, where the "meta.schema.json" brings all of them together:
      • Creation of a service-key using the default-policy while enforcing the usage of the field schema-version (only allowed value: "0.9").
      • Creation of a service-key with explicitly set policy while enforcing the usage of the field schema-version.
      • All the other use-cases that have been supported until now allowing to skip the schema-version. This is for backwards-compatibility.
  • Multi-schema support for binding-requests:
    • Separate logic between request-handling and request-parsing. Introduce the interface BindRequestParser for request-parsers. This is an architectural improvement to simplify maintenance even for a growing variance and seconds the seperation of the internal representation of scaling-policies from their json-representation.
    • Adapt tests to the slighly changed error-messages. This is necessary to support the old schema as well. For acceptance-tests these changes are minimal.
  • Makefiles:
    • Fix usage of shell-styles
    • (Re-)Generate fakes and gorouterproxy only if relevant input-files have changed.
    • Fix dependencies (e.g. for autoscaler.test) and recipes.
    • Use programs provided by devbox instead of go run ….
    • Fix target clean.
  • Efficiency-improvement: Avoid parsing of the default credential-type for the communication with the metricsforwarder on each request.

Current top-commit-id of that branch: "9ca68503e2f08bd8123df39b5722decb0fcde1bb"
@joergdw joergdw changed the title Fetch state from aas-release-branch "service-key-creation" Creation of service-keys Nov 1, 2025
@joergdw joergdw force-pushed the service-key-creation branch from 3e0f6c3 to d02e286 Compare November 17, 2025 15:11
@joergdw joergdw marked this pull request as ready for review November 19, 2025 08:35
This commit does adaptions to a slightly modified output due to switching to a multi-version-schema.
Comment on lines +4 to +5
aes_terminal_font_yellow := \033[38;2;255;255;0m
aes_terminal_reset := \033[0m
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For unknown reasons \e has stopped working on the shell. Similar adaptions that make these shell-styles work, follow.

Comment on lines +239 to +242
appResponse := cfh.CurlAppWithTimeout(
cfg, appName, fmt.Sprintf("/disk/%d/%d", spaceInMB, minutes), 10*time.Second)
expectedResponse := fmt.Sprintf("{\"minutes\":%d,\"utilization\":%d}", minutes, spaceInMB)
Expect(appResponse).Should(MatchJSON(expectedResponse))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change has been done to be able to track an error there using a debugger.

Comment on lines +82 to +92
defaultCustomMetricsCredentialType := &models.X509Certificate
if len(conf.DefaultCustomMetricsCredentialType) > 0 {
var err error
defaultCustomMetricsCredentialType, err = models.ParseCustomMetricsBindingAuthScheme(
conf.DefaultCustomMetricsCredentialType)
if err != nil {
logger.Fatal("parse-default-credential-type", err, lager.Data{
"default-credential-type": conf.DefaultCustomMetricsCredentialType,
})
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its best to set this default at this level. Before we validated the configuration's default-value on each request where we needed it.

Comment on lines +56 to +60
var _ error = &ValidationErrors{}

func (v ValidationErrors) Error() string {
func (v *ValidationErrors) Error() string {
var errs []string
for _, failure := range v {
for _, failure := range *v {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been done to support the error-handling in "broker.go:550-573" with harmonised error-types (i.e. they are now all pointers).

// 🚧 To-do: When making the type `models.PolicyDefinition` safe, then this validation should go
// into a functional constructor.
func (pv *PolicyValidator) validateAttributes(policy *models.PolicyDefinition, result *gojsonschema.Result) {
if policy == nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed for a simple service-key-creation using the default-policy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the more diverse json-schema for "bind"-requests, the validation against the schema can now return more errors. We need to adapt in the sense that we want to see if the expected one is in a list of potentially several ones that may occur.

Comment on lines +174 to +177
if noPolicyData := len(data) <= 0 ||
// In case we have policy-data we can rely on the presence of "instance_min_count" due to
// the json-schema.
!strings.Contains(string(data), `instance_min_count`); noPolicyData {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again to capture the case where one just wants to create a service-key using the default-policy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated but to satisfy our Java-linter.

@joergdw
Copy link
Contributor Author

joergdw commented Nov 20, 2025

All tests are green at the moment.

Copy link
Contributor

@geigerj0 geigerj0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just an intermediate review

if appGUID == "" {
err := errors.New("error: service must be bound to an application - service key creation is not supported")
logger.Error("check-required-app-guid", err)
appScalingConfig, err := b.bindingReqParser.Parse(details)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which mechanism ensures that a user does not create a service-key with an app-guid from a completely different org/space?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the app-guid, passed in the scaling policy, must always exist on CF already, right? this is kind of a pre-requisite to check, if the app belongs to the users org.

if this is not the case, we will never be able to solve the issue of "does the app belong to the users org"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is part of the task but has been forgotten. A fix will follow soon. Good catch! 👍🏻

}
apiErr := apiresponses.NewFailureResponseBuilder(
fmt.Errorf("invalid policy provided: %s", resultsJson),
http.StatusBadRequest, "failed-to-validate-policy").
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it is no valid JSON -> 400
if it does not stick to schema -> 422

Comment on lines +6 to +10
"anyOf": [
{"$ref": "./scaling-policy.schema.json"},
{"$ref": "./legacy.schema.json"},
{"$ref": "./service-key_only.schema.json"}
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anyOf allows everything, or? shouldn't this be oneOf

https://json-schema.org/understanding-json-schema/reference/combining#anyOf

the following test fails even though it shouldn't

				FIt("foo", func() {
					var bindingParams = []byte(`
						{
							"configuration": {
								"app_guid": "12345678-abcd-1234-5678-123456789abc",
								  "custom_metrics": {
									  "metric_submission_strategy": {
										  "allow_from": "bound_app"
									  }
								  }
							},
						  "instance_min_count": 1,
						  "instance_max_count": 5,
						  "scaling_rules": [
							{
							  "metric_type": "memoryused",
							  "threshold": 100,
							  "operator": "<",
							  "adjustment": "+1"
							}
						  ]
						}`)

					details = domain.BindDetails{
						AppGUID:       "", // No deprecated app GUID
						PlanID:        "some_plan-id",
						ServiceID:     "some_service-id",
						BindResource:  nil, // No BindResource for service keys
						RawParameters: bindingParams,
					}

					// Execution
					_, err := aBroker.Bind(ctx, instanceID, bindingID, details, false)

					Expect(err).NotTo(BeNil())
				})

Copy link
Contributor Author

@joergdw joergdw Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought a bit about this: We require backwards-compatibility. When you define anyOf in combination with the legacy-schema, then it is (trivially) guaranteed that you have backwards compatibility because the legacy-schema is one of the entries in the anyOf-construction. When you use oneOf it is not so trivial. And it has the potential to be wrong: Our legacy-schema is quite open to extension (due to missing "additionalProperties": false all over the file) – and therefore may overlap with newer schemas.

This is indeed a problem. As this test-case shows we currently can not distinguish in some cases between legacy-schema and the newer schema. I think I have found a different solution: I restrict the usage of schema-version in the legacy-schema to the single value null. By that it will be always distinguishable on this single field from other schema-versions.

…ter;

Background: Distinguishability from the newer schemas which all should have that field filled out with sensible values.
@joergdw joergdw force-pushed the service-key-creation branch from 0f30adc to e2b2214 Compare November 21, 2025 17:36
@joergdw joergdw force-pushed the service-key-creation branch from e1dd497 to cb6674d Compare November 24, 2025 16:11
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants