Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ingress): automatic host and path discovery #49

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

akosveres
Copy link
Collaborator

  • Add ability to create API Checks based on ingress resource host and
    path entries
  • Determine how internal resources are linked together
  • Add logic to add / update / delete internal ApiCheck resources
  • Small code improvements
  • Update docs and samples based on new code changes

@akosveres
Copy link
Collaborator Author

akosveres commented Nov 4, 2024

Tests are broken, need fixing and update.

Comment on lines 7 to +12
k8s.checklyhq.com/enabled: "true"
k8s.checklyhq.com/path: "/baz"
# k8s.checklyhq.com/endpoint: "foo.baaz" - Default read from spec.rules[0].host
# k8s.checklyhq.com/success: "200" - Default "200"
# k8s.checklyhq.com/endpoint: "foo.baaz" - Default read from spec.rules[*].host
k8s.checklyhq.com/group: "group-sample"
# k8s.checklyhq.com/muted: "false" # If not set, default "true"
# k8s.checklyhq.com/path: "/baz" - Default read from spec.rules[*].http.paths[*].path
# k8s.checklyhq.com/success: "200" - Default "200"
Copy link

Choose a reason for hiding this comment

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

With that it seems we can add only once custom endpoint+path+status. I'd suggest to have one annotation that can define multiple targets. E.g.:

  annotations:
    k8s.checklyhq.com/api_monitor: "200|https://foo.baaz/baz,301|http://redirect.demo"

Even better, maybe include JSON in the value that is directly tied to API.

JSON for flexibility, simple syntax for UX. Should be decided, but I'd move from ability to set only one endpoint.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

100% agree that we need something, not sure what the best path forward is, I'd hate to manage JSON inside YAML..

Copy link

Choose a reason for hiding this comment

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

Then we need to see if we can find best simple abstraction of what we want to achieve here and not be slapped in the face because it does not support some basic functionality. I'd still have API reference in mind here to make sure we cover basics.

What do you propose?

Also, what would be the minimum (original) API JSON request for hostname+path? Just to see verbosity. Benefit here is the ability to support those kind of annotations even in Service resources and documentation is already available.

@danielpaulus
Copy link

Seems like the tests are failing still.
@benben can we start dogfooding this operator? I'd like to give some better feedback on these PRs.

@akosveres
Copy link
Collaborator Author

Seems like the tests are failing still. @benben can we start dogfooding this operator? I'd like to give some better feedback on these PRs.

Tests are passing now.

* Add ability to create API Checks based on ingress resource host and
  path entries
* Determine how internal resources are linked together
* Add logic to add / update / delete internal ApiCheck resources
* Small code improvements
* Updated based on the new code updates
* ghcr.io is getting constant rate limits
* if the resource annotation is no longer present
* if the enabled annotation is changed to false
Comment on lines 327 to 333
_, exists := newApiChecksMap[existingApiCheck.Name]
if exists {
if existingApiCheck.Spec == newApiChecksMap[existingApiCheck.Name].Spec {
logger.Info("ApiCheck data is identical, no need for update", "ApiCheck Name", existingApiCheck.Name)
} else {
logger.Info("ApiCheck data is not identical, update needed", "ApiCheck Name", existingApiCheck.Name, "old spec", existingApiCheck.Spec, "new spec", newApiChecksMap[existingApiCheck.Name].Spec)
updateApiChecks = append(updateApiChecks, newApiChecksMap[existingApiCheck.Name])
Copy link

Choose a reason for hiding this comment

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

I think it would be better to just use the map value you've retrieved instead of constantly accessing the value through the map by name, which is prone to mistakes.

Suggested change
_, exists := newApiChecksMap[existingApiCheck.Name]
if exists {
if existingApiCheck.Spec == newApiChecksMap[existingApiCheck.Name].Spec {
logger.Info("ApiCheck data is identical, no need for update", "ApiCheck Name", existingApiCheck.Name)
} else {
logger.Info("ApiCheck data is not identical, update needed", "ApiCheck Name", existingApiCheck.Name, "old spec", existingApiCheck.Spec, "new spec", newApiChecksMap[existingApiCheck.Name].Spec)
updateApiChecks = append(updateApiChecks, newApiChecksMap[existingApiCheck.Name])
newApiCheck, exists := newApiChecksMap[existingApiCheck.Name]
if exists {
if existingApiCheck.Spec == newApiCheck.Spec {
logger.Info("ApiCheck data is identical, no need for update", "ApiCheck Name", existingApiCheck.Name)
} else {
logger.Info("ApiCheck data is not identical, update needed", "ApiCheck Name", existingApiCheck.Name, "old spec", existingApiCheck.Spec, "new spec", newApiCheck.Spec)
updateApiChecks = append(updateApiChecks, newApiCheck)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in latest commit.

continue
}

logger.Info("ApiCheck resource deleted successfully.", apiCheckResource.Name, "Namespace:", apiCheckResource.Namespace)
Copy link

Choose a reason for hiding this comment

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

Super nitpicky but it would be nice to use a consistent format for log messages. Either always end in punctuation or never do. Also I don't think it's necessary to include the : in any log key names.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in latest commit.

continue
}

logger.Info("ApiCheck resource is present, we need to delete it..", "Ingress Name", ingress.Name, "Ingress namespace", ingress.Namespace)
Copy link

Choose a reason for hiding this comment

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

Nitpick but you have double punctuation at the end here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in latest commit.

Comment on lines +308 to +309
labels := make(map[string]*string)
labels["ingress-controller"] = &ingress.Name
Copy link

Choose a reason for hiding this comment

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

This labels variable doesn't seem to be used for anything?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is used when we create the check on checklyhq.com, all labels are added as tags, it helps identifying in the UI where the checks are coming from and in this case, you could see from the tags that it was generated from a specific ingess.

Copy link

Choose a reason for hiding this comment

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

No, I mean that the variable is literally unused. The label map is created, and then a value is set, but then the labels variable is not used for anything, so there is no way for it to end up in the UI. Frankly I'm a little surprised that Go doesn't complain about the variable being unused.

Comment on lines 234 to 239
var host string
if ingress.Annotations[annotationEndpoint] == "" {
host = rule.Host
} else {
host = ingress.Annotations[annotationEndpoint]
}
Copy link

Choose a reason for hiding this comment

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

So this pattern seem to be quite prevalent in the codebase, if you prefer it the way it is then alright, but I would probably just do this instead:

Suggested change
var host string
if ingress.Annotations[annotationEndpoint] == "" {
host = rule.Host
} else {
host = ingress.Annotations[annotationEndpoint]
}
host := ingress.Annotations[annotationEndpoint]
if host == "" {
host = rule.Host
}

Always good if you're able to reduce redundant map accesses because it gives you less opportunity to use the wrong key.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in latest commit.

Comment on lines 85 to 86
_, checklyAnnotationExists := ingress.Annotations[annotationEnabled]
if (!checklyAnnotationExists) || (ingress.Annotations[annotationEnabled] == "false") {
Copy link

Choose a reason for hiding this comment

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

Seems kinda complex. Given how the exists variable is single use here, you might as well just put them in the if's scope. That way you can give them short generic names and it's not going to pollute entire scope. Also, minus one map access (my favorite).

Suggested change
_, checklyAnnotationExists := ingress.Annotations[annotationEnabled]
if (!checklyAnnotationExists) || (ingress.Annotations[annotationEnabled] == "false") {
if value, exists := ingress.Annotations[annotationEnabled]; !exists || value == "false" {

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in latest commit.

},
Spec: apiCheckSpec,
// Update API checks
if len(updateApiChecks) > 0 {
Copy link

Choose a reason for hiding this comment

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

You don't really need these len checks, the for ... range will work just fine without it. They're not hurting anything but you'd be able to reduce the nesting level which would make the code a bit easier to read.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in latest commit.

Comment on lines +249 to +259
for _, rulePath := range rule.HTTP.Paths {
if ingress.Annotations[annotationPath] == "" {
if rulePath.Path == "" {
paths = append(paths, "/")
} else {
paths = append(paths, rulePath.Path)
}
} else {
paths = append(paths, ingress.Annotations[annotationPath])
}
}
Copy link

Choose a reason for hiding this comment

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

I'm not sure if the logic here is correct. It might be, but it seems a little fishy. If there are multiple rule.HTTP.Paths, then you'll iterate over them, but then if the annotationPath exists, you add the same value (annotationPath's value) to paths in every iteration? Would that not basically just create the exact same check multiple times?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That is correct. I guess the right thing to do is to completely remove the path annotation. Do you agree?

Copy link

Choose a reason for hiding this comment

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

Since the path annotation used to be required, removing it would be a breaking change. I would rather not do that unless there is clear, worthy benefit, and I don't think there is (to be clear the feature in the PR is great, just saying I don't think removing the annotation is warranted). Would the following make sense instead:

If ingress.Annotations[annotationPath] exists, then we simply append its value to paths without looping through rule.HTTP.Paths at all. And if it is not set, then we do loop through rule.HTTP.Paths like now, except with all the annotation checking removed.

Thoughts?

} else {
logger.Info(
"ApiCheck data is not identical, update needed", "ApiCheck Name", existingApiCheck.Name, "old spec", existingApiCheck.Spec, "new spec", newApiCheck.Spec)
updateApiChecks = append(updateApiChecks, newApiChecksMap[existingApiCheck.Name])
Copy link

Choose a reason for hiding this comment

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

Suggested change
updateApiChecks = append(updateApiChecks, newApiChecksMap[existingApiCheck.Name])
updateApiChecks = append(updateApiChecks, newApiCheck)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants