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(apim): crds and controller for configuring apis in apim #1175

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

Conversation

tjololo
Copy link
Member

@tjololo tjololo commented Dec 13, 2024

Sorry, yet another HUGE pull request. Once the first version is out the size will hopefully change!

Description

CRD and Controller for managing API specific resources.

Related Issue(s)

Verification

  • Your code builds clean without any errors or warnings
  • Manual testing done (required)
  • Relevant automated test added (if you find this hard, leave it and we'll help out)
  • All tests run green

Documentation

  • User documentation is updated with a separate linked PR in altinn-studio-docs. (if applicable)

Summary by CodeRabbit

  • New Features

    • Introduced a Custom Resource Definition (CRD) for managing APIs and API versions, enhancing the framework for API management.
    • Added new fields to the Backend resource, including title, description, and url.
    • Enhanced the API versioning model with detailed specifications and management capabilities.
  • Bug Fixes

    • Improved error handling and logging in the reconciliation processes for APIs and API versions.
  • Tests

    • Added comprehensive unit tests for new functionalities, including base64 encoding and SHA256 hashing.
    • Established a mock implementation for Azure API Management functionalities to facilitate testing.
  • Chores

    • Updated the .gitignore file to exclude specific patterns.
    • Removed obsolete backend resource definitions.

Still some work to be done with the tests. Committing to not loose work
add base64-encoded string of api spec (content) to status.
refactor internal/utils after new methods are added.
fix bug in long_running_operations checker
Copy link

coderabbitai bot commented Dec 13, 2024

📝 Walkthrough

Walkthrough

The pull request introduces several significant changes to the dis-apim-operator service, including updates to API specifications, the addition of new Custom Resource Definitions (CRDs), enhancements to controller logic, and the introduction of utility functions for handling base64 and SHA256 operations. Key modifications include new fields in the ApiSpec, ApiVersionSpec, and Backend structures, as well as the addition of new methods for managing API versions and long-running operations in Azure. Additionally, several tests have been added or modified to ensure the reliability of the new functionalities.

Changes

File Path Change Summary
services/dis-apim-operator/.gitignore Added entry *.ignore.* to ignore specific files.
services/dis-apim-operator/api/v1alpha1/api_types.go Expanded ApiSpec and ApiStatus structures with new fields and methods for enhanced API definition and management. Removed Foo field.
services/dis-apim-operator/api/v1alpha1/apiversion_enums.go Introduced new types and constants related to API management, such as ContentFormat, APIContactInformation, and ProvisioningState. Added methods for Azure SDK compatibility.
services/dis-apim-operator/api/v1alpha1/apiversion_types.go Updated ApiVersionSpec and ApiVersionStatus structures with new fields. Added methods for version management and comparison.
services/dis-apim-operator/api/v1alpha1/backend_types.go Added new print columns "State" and "Age" to the Backend resource definition.
services/dis-apim-operator/api/v1alpha1/zz_generated.deepcopy.go Added and modified deepcopy methods for various structures to enhance deep copy functionality.
services/dis-apim-operator/cmd/main.go Updated ApiReconciler and ApiVersionReconciler setup to include new Azure client fields.
services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_apis.yaml Added new CRD for managing APIs with detailed schema definitions.
services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_apiversions.yaml Added new CRD for managing API versions with specified fields and behaviors.
services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_backends.yaml Enhanced Backend CRD with additional printer columns.
services/dis-apim-operator/config/rbac/role.yaml Updated ClusterRole to include new resources for managing APIs and versions.
services/dis-apim-operator/config/samples/apim_v1alpha1_api.yaml Defined a new API resource with detailed specifications and multiple versions.
services/dis-apim-operator/config/samples/apim_v1alpha1_backend.ignore.yaml Deleted file defining a Backend resource.
services/dis-apim-operator/config/samples/apim_v1alpha1_backend.yaml Added new fields to the Backend resource definition for enhanced metadata.
services/dis-apim-operator/internal/azure/long_running_operations.go Introduced functionality for managing long-running operations in Azure, including an OperationStatus type and a polling function.
services/dis-apim-operator/internal/azure/long_running_operations_test.go Added tests for the long-running operations functionality using Ginkgo framework.
services/dis-apim-operator/internal/controller/api_controller.go Enhanced ApiReconciler with new fields and methods for managing Azure API versions and finalizers.
services/dis-apim-operator/internal/controller/api_controller_test.go Improved tests for ApiReconciler with new assertions and resource management strategies.
services/dis-apim-operator/internal/controller/apiversion_controller.go Updated ApiVersionReconciler with new fields and methods for handling API version reconciliation.
services/dis-apim-operator/internal/controller/apiversion_controller_test.go Removed existing test suite for ApiVersion controller, indicating a change in testing strategy.
services/dis-apim-operator/internal/controller/backend_controller.go Updated BACKEND_FINALIZER constant for finalizer identification in reconciliation.
services/dis-apim-operator/internal/controller/backend_controller_test.go Restructured tests for backend reconciliation, focusing on direct interactions and improved state validation.
services/dis-apim-operator/internal/controller/suite_test.go Enhanced test suite setup with new Azure SDK components and reconciler configurations.
services/dis-apim-operator/internal/utils/base64.go Introduced utility functions for base64 encoding from both URL content and direct strings.
services/dis-apim-operator/internal/utils/base64_test.go Added unit tests for base64 utility functions using Ginkgo framework.
services/dis-apim-operator/internal/utils/policytemplate.go Added a function for generating policies from templates with error handling.
services/dis-apim-operator/internal/utils/policytemplate_test.go Introduced tests for the policy generation function, covering various scenarios.
services/dis-apim-operator/internal/utils/sha.go Added functions for SHA256 hashing from URL content and direct strings.
services/dis-apim-operator/internal/utils/sha_test.go Introduced tests for SHA256 utility functions, validating behavior across valid and invalid inputs.
services/dis-apim-operator/internal/utils/utils.go Added utility functions for URL validation and resource cleanup.
services/dis-apim-operator/internal/utils/utils_suite_test.go Established a test suite for the utils package with setup and teardown procedures.
services/dis-apim-operator/test/utils/azure_apim_api_fake.go Introduced a mock implementation for Azure API Management functionalities for testing.
services/dis-apim-operator/test/utils/azure_fake.go Removed outdated mock function and imports related to backend server simulation.

Suggested reviewers

  • khanrn
  • sduranc

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@tjololo tjololo marked this pull request as ready for review December 13, 2024 12:41
@tjololo tjololo requested a review from a team as a code owner December 13, 2024 12:41
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 25

🧹 Outside diff range and nitpick comments (40)
services/dis-apim-operator/internal/utils/sha_test.go (3)

52-58: Enhance URL error testing.

The invalid URL test case could be more comprehensive:

  1. Test with malformed URLs (e.g., "not-a-url")
  2. Be more specific about the expected error type
 	Context("with an invalid URL", func() {
-		It("should return an error", func() {
-			invalidUrl := "http://invalid-url"
-			_, err := Sha256FromContent(&invalidUrl)
-			Expect(err).To(HaveOccurred())
+		DescribeTable("should return appropriate errors",
+			func(url string, expectedError string) {
+				_, err := Sha256FromContent(&url)
+				Expect(err).To(HaveOccurred())
+				Expect(err.Error()).To(ContainSubstring(expectedError))
+			},
+			Entry("invalid hostname", "http://invalid-url", "no such host"),
+			Entry("malformed URL", "not-a-url", "unsupported protocol"),
+		)

43-43: Fix typo in test description.

There's a double space in the test description.

-		It("should return correct SHA256  when empty string content", func() {
+		It("should return correct SHA256 when empty string content", func() {

19-20: Consider using constants for test data.

Test values and their expected hashes are repeated throughout the tests. Consider extracting them to constants for better maintainability.

+const (
+	testContent = "test content"
+	testContentHash = "6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72"
+	emptyContentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+)

Also applies to: 29-30, 44-46

services/dis-apim-operator/internal/utils/sha.go (1)

25-26: Improve function documentation with parameter and return value descriptions

The current documentation could be more descriptive about the parameters and return values.

Consider updating the documentation:

-// Sha256FromContent returns the SHA256 hash of the given content. If the content is a URL, it will fetch the content and return the SHA256 hash.
+// Sha256FromContent computes the SHA256 hash of the provided content.
+// Parameters:
+//   content: Pointer to a string containing either direct content or a URL. If nil, returns empty string.
+// Returns:
+//   string: Hexadecimal representation of the SHA256 hash, or empty string if content is nil
+//   error: Any error encountered during content retrieval or hashing
services/dis-apim-operator/internal/utils/utils.go (1)

1-6: Consider a more specific package name and separation of concerns.

The package name 'utils' is quite generic. Consider:

  1. Renaming to something more specific like ioutils or urlutils based on primary purpose
  2. Splitting URL and I/O utilities into separate packages to follow single responsibility principle
services/dis-apim-operator/internal/controller/backend_controller_test.go (2)

61-63: Remove duplicate assertion of ProvisioningState

The assertion on line 63 is redundant as it repeats the check already performed on line 61. Removing the duplicate assertion will make the test more concise.

Apply this diff to remove the duplicate assertion:

 g.Expect(err).NotTo(HaveOccurred())
 g.Expect(updatedBackend.Status.ProvisioningState).To(Equal(apimv1alpha1.BackendProvisioningStateSucceeded))
 g.Expect(updatedBackend.Status.BackendID).To(Equal("/subscriptions/fake-subscription/resourceGroups/fake-resource-group/providers/APIM/Backend/" + updatedBackend.GetAzureResourceName()))
-g.Expect(updatedBackend.Status.ProvisioningState).To(Equal(apimv1alpha1.BackendProvisioningStateSucceeded))
 g.Expect(fakeApim.Backends).To(HaveLen(1))

68-72: Simplify resource update by removing unnecessary Eventually block

Using Eventually to perform a resource update is unnecessary since updates are immediate operations. You can perform the update directly without wrapping it in Eventually.

Apply this diff to simplify the code:

-Eventually(func(g Gomega) {
     updatedBackend.Spec.Url = "https://updated.example.com"
     err := k8sClient.Update(ctx, updatedBackend)
-    Expect(err).NotTo(HaveOccurred())
-}, timeout, interval).Should(Succeed())
+updatedBackend.Spec.Url = "https://updated.example.com"
+err := k8sClient.Update(ctx, updatedBackend)
+Expect(err).NotTo(HaveOccurred())
services/dis-apim-operator/api/v1alpha1/api_types.go (1)

65-73: Use pointers for optional fields in ApiStatus

To accurately represent the presence or absence of optional fields, it's better to use pointers in the ApiStatus struct.

Consider updating the types as follows:

-	ProvisioningState ProvisioningState `json:"provisioningState,omitempty"`
+	ProvisioningState *ProvisioningState `json:"provisioningState,omitempty"`

-	ApiVersionSetID string `json:"apiVersionSetID,omitempty"`
+	ApiVersionSetID *string `json:"apiVersionSetID,omitempty"`
services/dis-apim-operator/api/v1alpha1/apiversion_enums.go (4)

14-33: Consider grouping related constants

For better readability and maintainability, group related constants using iota or logical grouping.

Example:

const (
	// OpenAPI formats
	ContentFormatOpenapi       ContentFormat = "openapi"
	ContentFormatOpenapiJSON   ContentFormat = "openapi+json"
	ContentFormatOpenapiLink   ContentFormat = "openapi-link"
	ContentFormatOpenapiJSONLink ContentFormat = "openapi+json-link"
	// Swagger formats
	ContentFormatSwaggerJSON     ContentFormat = "swagger-json"
	ContentFormatSwaggerLinkJSON ContentFormat = "swagger-link-json"
	// Other formats
	ContentFormatGraphqlLink ContentFormat = "graphql-link"
	ContentFormatWadlLinkJSON ContentFormat = "wadl-link-json"
	ContentFormatWadlXML      ContentFormat = "wadl-xml"
)

40-49: Add validation for APIContactInformation fields

Consider adding validation annotations to ensure the fields match the expected formats, such as email and URL.

Example:

// +kubebuilder:validation:Format=email
Email *string `json:"email,omitempty"`

// +kubebuilder:validation:Format=uri
URL *string `json:"url,omitempty"`

62-71: Use consistent naming for versioning scheme constants

Ensure that the constants for APIVersionScheme match the naming conventions and consider simplifying the constant names.

Example:

const (
	VersioningSchemeHeader  APIVersionScheme = "Header"
	VersioningSchemeQuery   APIVersionScheme = "Query"
	VersioningSchemeSegment APIVersionScheme = "Segment"
)

89-97: Avoid unnecessary pointer receivers

If Protocol is a string type, methods like AzureProtocol() might not need a pointer receiver unless mutation is required.

Change the method receiver to value type if mutation is not necessary:

-func (p *Protocol) AzureProtocol() *apim.Protocol {
+func (p Protocol) AzureProtocol() *apim.Protocol {
services/dis-apim-operator/internal/controller/apiversion_controller.go (2)

156-220: Consolidate error handling in createUpdateApimApi

The method has multiple error checks and status updates. Consolidate error handling to reduce repetition and potential mistakes.

Example:

  • Use a defer function to handle status updates based on the error variable.
  • Ensure that all exit points update the status appropriately.

264-285: Optimize the runPolicyTemplating method

Consider handling errors more gracefully if a backend resource is not found, and provide clearer error messages.

Example:

err := r.Get(context.Background(), client.ObjectKey{Name: v.IdFromBackend.Name, Namespace: namespace}, &backend)
if err != nil {
	if errors.IsNotFound(err) {
		return "", fmt.Errorf("backend %s/%s not found", namespace, v.IdFromBackend.Name)
	}
	return "", fmt.Errorf("failed to get backend %s/%s: %w", namespace, v.IdFromBackend.Name, err)
}
services/dis-apim-operator/internal/controller/api_controller.go (2)

45-48: Consider Dependency Injection for the APIM Client

The ApiReconciler struct now includes NewClient, ApimClientConfig, and apimClient fields. To enhance testability and adherence to the Dependency Injection principle, consider injecting the APIM client dependencies via the constructor or setup method rather than instantiating them within the reconciler.


179-179: Log Errors When Updating Api Status

When updating the Api status using r.Status().Update(ctx, api), if an error occurs, it's returned but not logged. For better observability and debugging, it's advisable to log the error.

Modify the code to log the error before returning:

if err := r.Status().Update(ctx, api); err != nil {
    logger.Error(err, "Failed to update Api status")
    return ctrl.Result{}, err
}
return ctrl.Result{}, nil
services/dis-apim-operator/test/utils/azure_apim_api_fake.go (1)

46-51: Unused Error Flags

The fields createUpdateServerError, deleteServerError, and getServerError are initialized but not used in the fake servers.

If these flags are intended for simulating errors, ensure they are correctly implemented in the methods. Otherwise, consider removing them to simplify the code.

services/dis-apim-operator/api/v1alpha1/apiversion_types.go (3)

65-65: Clarify the Usage of Name Field

The Name field in ApiVersionSubSpec is optional and defaults to "default" if not provided. However, the comment mentions that it will be the default version if no name is provided. This might be confusing.

Clarify the comment to explain the default behavior more explicitly.


188-194: Avoid Hardcoding Default Version Specifier

In GetApiVersionSpecifier, the default value "default" is hardcoded. This could lead to conflicts if "default" is a valid version name. Consider making the default value configurable or validate that "default" is acceptable.


264-272: Improve Pointer Comparison Utility

The function pointerValueEqual does not handle the case where the underlying values are pointers to complex types. If T is a struct or slice, this comparison may not work as intended.

Consider using reflect.DeepEqual for more robust comparison of complex types.

services/dis-apim-operator/internal/utils/policytemplate.go (1)

10-10: Consider adding template security options

The template parsing is using default options which might allow unsafe template operations. Consider using template.ParseFS() with a restricted file system or adding security restrictions to prevent template injection attacks.

services/dis-apim-operator/internal/utils/utils_suite_test.go (2)

17-21: Consider implementing the setup placeholder

The BeforeSuite block contains a TODO comment but no actual setup. Consider what test environment setup might be needed (e.g., test fixtures, mock configurations).


23-26: Consider implementing the teardown placeholder

The AfterSuite block contains a TODO comment but no actual teardown. Consider what cleanup might be needed (e.g., removing test data, closing connections).

services/dis-apim-operator/config/samples/apim_v1alpha1_api.yaml (2)

31-39: Enhance API policy configuration

The policy configuration is basic and could benefit from additional security and performance policies.

       policies:
         policyContent: |
           <policies>
             <inbound>
+              <check-header name="Authorization" failed-check-httpcode="401" />
               <set-backend-service base-url="http://example.com/api/4/" />
+              <cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none" />
               <base />
             </inbound>
+            <backend>
+              <retry condition="@(context.Response.StatusCode >= 500)" count="3" interval="10" max-interval="30" delta="5" />
+              <base />
+            </backend>
+            <outbound>
+              <cache-store duration="3600" />
+              <base />
+            </outbound>
           </policies>

52-52: Add newline at end of file

Add a newline character at the end of the file to comply with YAML standards.

🧰 Tools
🪛 yamllint (1.35.1)

[error] 52-52: no new line character at the end of file

(new-line-at-end-of-file)

services/dis-apim-operator/internal/utils/policytemplate_test.go (1)

8-54: Enhance test coverage with additional scenarios

While the current tests cover basic functionality and error cases, consider adding the following test scenarios for more comprehensive coverage:

  1. Complex nested templates with multiple levels
  2. Templates with multiple variables
  3. Special characters and HTML content that might need escaping
  4. Large template strings to verify performance

Example test case for multiple variables:

Context("with multiple template variables", func() {
    It("should generate the correct policy", func() {
        expected := "Hello, John Doe from Example Corp!"
        templateContent := "Hello, {{.FirstName}} {{.LastName}} from {{.Company}}!"
        data := map[string]string{
            "FirstName": "John",
            "LastName": "Doe",
            "Company": "Example Corp",
        }
        result, err := GeneratePolicyFromTemplate(templateContent, data)
        Expect(err).NotTo(HaveOccurred())
        Expect(result).To(Equal(expected))
    })
})
services/dis-apim-operator/internal/utils/base64_test.go (3)

15-17: Don't ignore error handling in test server

The test server ignores potential write errors which could mask issues. Consider handling or at least logging the error.

-				_, _ = fmt.Fprintln(w, "test content")
+				_, err := fmt.Fprintln(w, "test content")
+				if err != nil {
+					t.Logf("Error writing response: %v", err)
+				}

52-58: Improve invalid URL test reliability

The current invalid URL test might be unreliable as "invalid-url" could potentially resolve. Consider using a guaranteed invalid URL scheme or domain.

-			invalidUrl := "http://invalid-url"
+			invalidUrl := "invalid-scheme://localhost"

12-59: Add test cases for additional scenarios

Consider adding tests for:

  1. Large content handling to verify memory efficiency
  2. Network timeouts
  3. Non-200 HTTP responses
  4. Different content types (JSON, XML, binary)

Example test for non-200 response:

Context("with non-200 HTTP response", func() {
    It("should return an error", func() {
        server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.WriteHeader(http.StatusInternalServerError)
        }))
        defer server.Close()
        _, err := Base64FromContent(&server.URL)
        Expect(err).To(HaveOccurred())
        Expect(err.Error()).To(ContainSubstring("500"))
    })
})
services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_backends.yaml (1)

17-24: Consider adding shortNames and categories

While the printer columns addition improves observability, the CRD could benefit from additional metadata to improve CLI usability.

Add shortNames and categories to the CRD spec:

  names:
    kind: Backend
    listKind: BackendList
    plural: backends
    singular: backend
+   shortNames:
+   - bk
+   - apimbackend
+   categories:
+   - apim
+   - all
services/dis-apim-operator/internal/azure/long_running_operations_test.go (1)

65-80: Consider adding more in-progress scenarios.

While the basic in-progress case is covered, consider adding tests for:

  • Multiple in-progress states before success
  • Timeout scenarios
  • State transitions
services/dis-apim-operator/internal/controller/backend_controller.go (2)

Line range hint 89-117: Consider adding retry mechanism for APIM client operations.

The reconciliation logic could benefit from a retry mechanism when dealing with transient Azure API Management failures:

 if !backend.MatchesActualState(&azBackend) {
 	logger.Info("Backend does not match actual state, updating")
-	err := r.handleCreateUpdate(ctx, &backend)
+	err := utils.RetryWithExponentialBackoff(ctx, func() error {
+		return r.handleCreateUpdate(ctx, &backend)
+	}, 3, time.Second)
 	if err != nil {
 		logger.Error(err, "Failed to update backend")
 		return ctrl.Result{}, err
 	}
 	logger.Info("Backend updated")
 	return ctrl.Result{RequeueAfter: 1 * time.Minute}, nil
 }

Line range hint 156-186: Enhance error handling in handleDeletion.

The deletion logic could be improved to handle various edge cases:

 func (r *BackendReconciler) handleDeletion(ctx context.Context, backend *apimv1alpha1.Backend) error {
 	logger := log.FromContext(ctx)
+	// Add timeout context
+	ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
+	defer cancel()
+
 	azureBackend, err := r.apimClient.GetBackend(ctx, backend.GetAzureResourceName(), nil)
 	if err != nil {
 		if azure.IsNotFoundError(err) {
 			controllerutil.RemoveFinalizer(backend, BACKEND_FINALIZER)
 			if err := r.Update(ctx, backend); err != nil {
+				// Add specific error type for client update failures
+				return fmt.Errorf("failed to remove finalizer after backend not found: %w", err)
-				logger.Error(err, "Failed to remove finalizer")
-				return err
 			}
 			return nil
 		}
-		logger.Error(err, "Failed to get backend for deletion")
-		return err
+		return fmt.Errorf("failed to get backend for deletion: %w", err)
 	}
+
+	// Add validation for required fields
+	if azureBackend.ETag == nil {
+		return fmt.Errorf("backend ETag is nil")
+	}
+
 	resp, err := r.apimClient.DeleteBackend(ctx, backend.GetAzureResourceName(), *azureBackend.ETag, nil)
 	if err != nil {
-		logger.Error(err, fmt.Sprintf("Failed to delete backend. backend: %#v", azureBackend))
-		return err
+		return fmt.Errorf("failed to delete backend %s: %w", backend.GetAzureResourceName(), err)
 	}
 	logger.Info("Backend deleted", "response", resp)
+
 	controllerutil.RemoveFinalizer(backend, BACKEND_FINALIZER)
 	if err := r.Update(ctx, backend); err != nil {
-		logger.Error(err, "Failed to remove finalizer")
-		return err
+		return fmt.Errorf("failed to remove finalizer after successful deletion: %w", err)
 	}
 	return nil
 }
services/dis-apim-operator/internal/controller/api_controller_test.go (3)

51-89: Consider adding error scenario test cases

While the happy path is well tested, consider adding test cases for:

  • Invalid API specifications
  • Version set creation failures
  • Concurrent version updates

91-103: Enhance test readability with state transition descriptions

Consider adding more descriptive messages in the Eventually blocks to clarify the expected state transitions. This will make test failures more informative.

 Eventually(func(g Gomega) {
   g.Expect(fakeApim.APIMVersionSets).To(HaveLen(1))
   g.Expect(k8sClient.List(ctx, &apiVersionList)).Should(Succeed())
   g.Expect(apiVersionList.Items).To(HaveLen(1))
-  g.Expect(*apiVersionList.Items[0].Spec.Content).To(Equal(`{"openapi": "3.0.0","info": {"title": "Minimal API v1","version": "1.0.0"},""paths": {"test": "test"}}`))
+  g.Expect(*apiVersionList.Items[0].Spec.Content).To(Equal(`{"openapi": "3.0.0","info": {"title": "Minimal API v1","version": "1.0.0"},""paths": {"test": "test"}}`),
+    "API version content should be updated with new paths")

140-146: Consider parameterizing timeout in helper function

The helper function could be more flexible by accepting a custom timeout parameter, allowing different wait times for different scenarios.

-func getUpdatedApi(ctx context.Context, typeNamespacedName types.NamespacedName) apimv1alpha1.Api {
+func getUpdatedApi(ctx context.Context, typeNamespacedName types.NamespacedName, customTimeout time.Duration) apimv1alpha1.Api {
   resource := apimv1alpha1.Api{}
   Eventually(func(g Gomega) {
     g.Expect(k8sClient.Get(ctx, typeNamespacedName, &resource)).To(Succeed())
-  }, timeout, interval).Should(Succeed())
+  }, customTimeout, interval).Should(Succeed())
   return resource
 }
services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_apiversions.yaml (2)

46-192: Consider adding additional field validations

While the schema is comprehensive, consider adding:

  • Pattern validation for path field to ensure valid URL paths
  • Maximum length validation for description and displayName
  • Enum validation for contentFormat
 path:
   description: Path - API prefix. The value is combined with the API
     version to form the URL of the API endpoint.
+  pattern: ^/[a-zA-Z0-9-_/]*$
+  maxLength: 255
   type: string

193-215: Add enum values for provisioningState

Consider adding an enum for the provisioningState field to ensure valid state transitions and improve validation.

 provisioningState:
   description: 'ProvisioningState - The provisioning state of the API.
     Possible values are: Creating, Succeeded, Failed, Updating, Deleting,
     and Deleted.'
+  enum:
+    - Creating
+    - Succeeded
+    - Failed
+    - Updating
+    - Deleting
+    - Deleted
   type: string
services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_apis.yaml (1)

46-218: Consider enhancing field validations

The spec structure is well-defined, but could benefit from additional validations:

  1. Add pattern validation for path field to ensure valid URL paths
  2. Add maxLength validation for displayName
  3. Add enum validation for allowed protocol values
              path:
                description: Path - API prefix. The value is combined with the API
                  version to form the URL of the API endpoint.
                type: string
+               pattern: '^[a-zA-Z0-9-_/]+$'
+               maxLength: 255
              displayName:
                description: DisplayName - The display name of the API. This name
                  is used by the developer portal as the API name.
                type: string
+               maxLength: 128
              protocols:
                default:
                - https
                description: Protocols - Describes protocols over which API
                  is made available. Default value is https.
                items:
                  type: string
+                 enum:
+                 - http
+                 - https
services/dis-apim-operator/api/v1alpha1/zz_generated.deepcopy.go (1)

Line range hint 1-519: LGTM! Auto-generated deepcopy implementations

The generated code correctly implements deep copying for all types, with proper handling of nil pointers, maps, and slices.

Remember to run make generate when making changes to the API types to keep this file in sync.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2ab065 and f419394.

📒 Files selected for processing (33)
  • services/dis-apim-operator/.gitignore (1 hunks)
  • services/dis-apim-operator/api/v1alpha1/api_types.go (3 hunks)
  • services/dis-apim-operator/api/v1alpha1/apiversion_enums.go (1 hunks)
  • services/dis-apim-operator/api/v1alpha1/apiversion_types.go (3 hunks)
  • services/dis-apim-operator/api/v1alpha1/backend_types.go (1 hunks)
  • services/dis-apim-operator/api/v1alpha1/zz_generated.deepcopy.go (7 hunks)
  • services/dis-apim-operator/cmd/main.go (1 hunks)
  • services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_apis.yaml (1 hunks)
  • services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_apiversions.yaml (1 hunks)
  • services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_backends.yaml (1 hunks)
  • services/dis-apim-operator/config/rbac/role.yaml (2 hunks)
  • services/dis-apim-operator/config/samples/apim_v1alpha1_api.yaml (1 hunks)
  • services/dis-apim-operator/config/samples/apim_v1alpha1_backend.ignore.yaml (0 hunks)
  • services/dis-apim-operator/config/samples/apim_v1alpha1_backend.yaml (1 hunks)
  • services/dis-apim-operator/internal/azure/long_running_operations.go (1 hunks)
  • services/dis-apim-operator/internal/azure/long_running_operations_test.go (1 hunks)
  • services/dis-apim-operator/internal/controller/api_controller.go (2 hunks)
  • services/dis-apim-operator/internal/controller/api_controller_test.go (1 hunks)
  • services/dis-apim-operator/internal/controller/apiversion_controller.go (3 hunks)
  • services/dis-apim-operator/internal/controller/apiversion_controller_test.go (0 hunks)
  • services/dis-apim-operator/internal/controller/backend_controller.go (1 hunks)
  • services/dis-apim-operator/internal/controller/backend_controller_test.go (2 hunks)
  • services/dis-apim-operator/internal/controller/suite_test.go (3 hunks)
  • services/dis-apim-operator/internal/utils/base64.go (1 hunks)
  • services/dis-apim-operator/internal/utils/base64_test.go (1 hunks)
  • services/dis-apim-operator/internal/utils/policytemplate.go (1 hunks)
  • services/dis-apim-operator/internal/utils/policytemplate_test.go (1 hunks)
  • services/dis-apim-operator/internal/utils/sha.go (1 hunks)
  • services/dis-apim-operator/internal/utils/sha_test.go (1 hunks)
  • services/dis-apim-operator/internal/utils/utils.go (1 hunks)
  • services/dis-apim-operator/internal/utils/utils_suite_test.go (1 hunks)
  • services/dis-apim-operator/test/utils/azure_apim_api_fake.go (1 hunks)
  • services/dis-apim-operator/test/utils/azure_fake.go (0 hunks)
💤 Files with no reviewable changes (3)
  • services/dis-apim-operator/config/samples/apim_v1alpha1_backend.ignore.yaml
  • services/dis-apim-operator/internal/controller/apiversion_controller_test.go
  • services/dis-apim-operator/test/utils/azure_fake.go
✅ Files skipped from review due to trivial changes (1)
  • services/dis-apim-operator/.gitignore
🧰 Additional context used
🪛 yamllint (1.35.1)
services/dis-apim-operator/config/samples/apim_v1alpha1_api.yaml

[error] 52-52: no new line character at the end of file

(new-line-at-end-of-file)

🔇 Additional comments (23)
services/dis-apim-operator/internal/utils/sha_test.go (2)

1-10: LGTM! Package and imports are well-structured.

The imports are appropriate and follow the standard testing patterns for Ginkgo/Gomega.


12-59: Well-structured test suite with comprehensive coverage.

The test suite is well-organized with good coverage of various scenarios including:

  • URL content hashing
  • String content hashing
  • Edge cases (nil, empty string)
  • Error handling

The use of Ginkgo's BDD-style testing provides clear test organization and readability.

services/dis-apim-operator/internal/utils/sha.go (1)

30-33: Verify URL validation implementation

Let's verify the implementation of the isUrl helper function to ensure proper URL validation.

✅ Verification successful

URL validation implementation is secure and follows Go standards

The isUrl function uses Go's standard url.ParseRequestURI function, which provides robust URL validation. This implementation:

  • Properly validates URL syntax according to RFC 3986
  • Returns a boolean indicating if the string is a valid URL
  • Uses the standard library instead of custom regex or string manipulation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the implementation of isUrl function
# Expected: Find the implementation and verify it handles various URL formats correctly

# Search for isUrl function implementation
ast-grep --pattern 'func isUrl($$$) $$$'

# Backup: Use ripgrep if ast-grep doesn't find it
rg -A 10 'func isUrl'

Length of output: 909

services/dis-apim-operator/internal/utils/utils.go (2)

13-15: 🛠️ Refactor suggestion

Document error handling strategy and consider logging.

While ignoring close errors can be appropriate in some cases, it's important to:

  1. Add documentation explaining when to use this function vs handling errors
  2. Consider adding debug logging for troubleshooting
  3. Evaluate if some errors should not be ignored (e.g., during critical operations)
+// closeIgnoreError closes an io.Closer and intentionally ignores any errors.
+// Use this only for cleanup operations where failure to close is acceptable,
+// such as closing response bodies after reading.
 func closeIgnoreError(c io.Closer) {
-	_ = c.Close()
+	if err := c.Close(); err != nil {
+		// TODO: Consider adding debug level logging here
+	}
 }

Let's check the usage context to understand the risk level of ignoring errors:


8-11: 🛠️ Refactor suggestion

Enhance URL validation function implementation.

Several improvements are recommended:

  1. Rename to follow Go conventions: isUrlisURL
  2. Consider exporting if used by other packages
  3. Add documentation explaining validation criteria
  4. Consider additional validation for specific URL requirements (e.g., HTTPS only, valid hostname)
-func isUrl(s string) bool {
+// isURL checks if the given string is a valid URL.
+// It uses url.ParseRequestURI for basic URL validation.
+func isURL(s string) bool {
 	_, err := url.ParseRequestURI(s)
 	return err == nil
 }

Let's check if this function is used elsewhere to determine if it should be exported:

✅ Verification successful

Function should remain unexported but follow Go naming conventions

The isUrl function is only used internally within the utils package by two exported functions: Base64FromContent and Sha256FromContent. While the package itself is imported by other packages, the isUrl function is not directly used outside its package. Therefore:

  1. The function should remain unexported
  2. Rename to follow Go conventions: isUrlisURL
  3. Add documentation as suggested
-func isUrl(s string) bool {
+// isURL checks if the given string is a valid URL using url.ParseRequestURI
+func isURL(s string) bool {
 	_, err := url.ParseRequestURI(s)
 	return err == nil
 }

Note: The current URL validation is sufficient for its use case in Base64FromContent and Sha256FromContent, where it's only used to determine if content should be fetched from a URL.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for isUrl usage across the codebase
rg "isUrl\(" --type go

Length of output: 248


Script:

#!/bin/bash
# Let's check the usage context in these files
echo "=== base64.go ==="
rg -B 5 -A 5 "isUrl\(" services/dis-apim-operator/internal/utils/base64.go

echo -e "\n=== sha.go ==="
rg -B 5 -A 5 "isUrl\(" services/dis-apim-operator/internal/utils/sha.go

# Check if this package is imported by other packages
echo -e "\n=== Package imports ==="
rg "\".*\/internal\/utils\"" --type go

Length of output: 1175


Script:

#!/bin/bash
# Check if this package is imported by other packages (fixing the regex)
echo "=== Package imports ==="
rg "internal/utils" --type go | grep -v "^services/dis-apim-operator/internal/utils"

# Let's also check if there are any exported functions in this package that use isUrl
echo -e "\n=== Exported functions using isUrl ==="
ast-grep --pattern 'func $FUNC($$$) $$$' services/dis-apim-operator/internal/utils/*.go | grep -B2 -A2 "isUrl"

Length of output: 3371

services/dis-apim-operator/internal/controller/suite_test.go (8)

26-31: Imports are appropriate and necessary

The added imports are relevant for the new functionalities introduced, including Azure SDK components and test utilities.


52-52: Initialization of new variables is correct

The declaration of k8sManager and fakeApim is appropriate and follows Go conventions.

Also applies to: 56-56


92-92: Proper initialization of fakeApim

The fakeApim variable is correctly instantiated using testutils.NewFakeAPIMClientStruct(), facilitating mock Azure API Management interactions for testing.


97-102: Kubernetes manager setup is correctly implemented

The k8sManager is initialized without errors, and necessary checks ensure it's not nil.


103-109: Configuration of serverFactoryTransport is sound

The setup of serverFactoryTransport with the fake Azure API Management server factory is appropriate for simulating Azure interactions.


110-120: APIM client configuration is correctly defined

The apimClientConfig is properly constructed with mock Azure details and client options, ensuring compatibility with the fake Azure environment.


121-144: Reconcilers are set up correctly with the manager

The ApiVersionReconciler, BackendReconciler, and ApiReconciler are correctly initialized and registered with the Kubernetes manager, using the fake APIM client.


145-149: Manager start function handles errors appropriately

Starting the k8sManager within a goroutine includes error handling with GinkgoRecover() and checks to ensure the manager starts without issues.

services/dis-apim-operator/config/samples/apim_v1alpha1_backend.yaml (1)

9-11: Update Sample Backend Configuration

The addition of title, description, and url fields enhances the sample backend configuration. Ensure that this sample aligns with the latest CRD specifications and serves as a proper example for users.

services/dis-apim-operator/internal/utils/policytemplate.go (1)

15-23: LGTM! Efficient implementation using strings.Builder

The implementation correctly uses strings.Builder for memory-efficient string concatenation and properly handles template execution errors.

services/dis-apim-operator/config/rbac/role.yaml (1)

10-11: LGTM! Well-structured RBAC permissions

The new resources follow Kubernetes RBAC best practices with appropriate verb restrictions for different resource types (core resources, finalizers, and status subresources).

Let's verify that these permissions match the controller requirements:

Also applies to: 24-25, 32-33

✅ Verification successful

RBAC permissions correctly match controller requirements

The RBAC permissions in role.yaml perfectly align with the controller implementations:

  • API controller has kubebuilder RBAC markers that match the role:

    • apis resource with full CRUD permissions
    • apis/status with get, update, patch
    • apis/finalizers with update
  • APIVersion controller has matching RBAC markers for:

    • apiversions resource with full CRUD
    • apiversions/status with get, update, patch
    • apiversions/finalizers with update
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify controller uses these permissions
# Look for reconciler code that uses these new resources

# Check for API resource usage
rg -A 5 "apis|apiversions" services/dis-apim-operator/internal/controller/

# Check for finalizer usage
ast-grep --pattern 'SetupWithManager($_) {
  $$$
  Owns(&$_)
  $$$
}'

Length of output: 4276

services/dis-apim-operator/internal/utils/utils_suite_test.go (1)

12-15: LGTM! Well-structured test suite setup

The test suite follows Ginkgo best practices with proper initialization, logger configuration, and lifecycle hooks.

Also applies to: 17-21, 23-26

services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_backends.yaml (1)

17-24: LGTM on printer columns addition

The addition of State and Age columns will improve resource monitoring and troubleshooting capabilities.

services/dis-apim-operator/internal/azure/long_running_operations_test.go (1)

15-64: LGTM! Well-structured success case tests.

The test cases effectively cover different HTTP status codes (200, 201, 202) for successful operations using BDD style testing.

services/dis-apim-operator/internal/controller/api_controller_test.go (1)

34-37: LGTM: Test setup with appropriate timeout values

The timeout (60s) and interval (250ms) constants are well-chosen for integration tests, providing sufficient time for API operations while maintaining reasonable test execution times.

services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_apiversions.yaml (1)

1-24: LGTM: Well-structured CRD definition

The CRD follows Kubernetes best practices with proper metadata, versioning, and scope definition. The additional printer columns for State and Age improve resource visibility.

services/dis-apim-operator/config/crd/bases/apim.dis.altinn.cloud_apis.yaml (2)

1-24: LGTM! Well-structured CRD definition.

The CRD follows Kubernetes best practices with proper printer columns for status and age.


219-259: Review security implications of storing policies in status

The status field stores base64-encoded policies which might contain sensitive information. Consider:

  1. Implementing encryption for sensitive policy content
  2. Adding RBAC rules to restrict access to status subresource

Comment on lines +15 to +17
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintln(w, "test content")
}))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix inconsistent test server response.

The fmt.Fprintln adds a newline to the response, which affects the hash calculation. This makes the test brittle and potentially platform-dependent.

-				_, _ = fmt.Fprintln(w, "test content")
+				_, _ = fmt.Fprint(w, "test content")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintln(w, "test content")
}))
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprint(w, "test content")
}))

Comment on lines +11 to +23
func sha256FromUrlContent(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer closeIgnoreError(resp.Body)

h := sha256.New()
if _, err := io.Copy(h, resp.Body); err != nil {
return "", err
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add timeout and improve error handling in URL content retrieval

The current implementation has several potential issues that should be addressed:

  1. No timeout on HTTP requests could lead to hanging operations
  2. HTTP response status codes aren't validated
  3. TLS verification settings aren't explicitly configured

Consider implementing these improvements:

+var httpClient = &http.Client{
+    Timeout: 30 * time.Second,
+    Transport: &http.Transport{
+        TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
+    },
+}
+
 func sha256FromUrlContent(url string) (string, error) {
-    resp, err := http.Get(url)
+    resp, err := httpClient.Get(url)
     if err != nil {
         return "", err
     }
     defer closeIgnoreError(resp.Body)
 
+    if resp.StatusCode != http.StatusOK {
+        return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+    }
+
     h := sha256.New()
     if _, err := io.Copy(h, resp.Body); err != nil {
         return "", err
     }
     return fmt.Sprintf("%x", h.Sum(nil)), nil
 }

Don't forget to add these imports:

import (
    "crypto/tls"
    "time"
)

Comment on lines +26 to +37
func Sha256FromContent(content *string) (string, error) {
if content == nil {
return "", nil
}
if isUrl(*content) {

return sha256FromUrlContent(*content)
}
h := sha256.New()
h.Write([]byte(*content))
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add content size validation to prevent memory exhaustion

The function should implement size limits for both direct content and URL responses to prevent potential memory exhaustion.

Consider implementing size limits:

+const maxContentSize = 50 * 1024 * 1024 // 50MB limit
+
 func Sha256FromContent(content *string) (string, error) {
     if content == nil {
         return "", nil
     }
+    if len(*content) > maxContentSize {
+        return "", fmt.Errorf("content size exceeds maximum allowed size of %d bytes", maxContentSize)
+    }
     if isUrl(*content) {
         return sha256FromUrlContent(*content)
     }
     h := sha256.New()
     h.Write([]byte(*content))
     return fmt.Sprintf("%x", h.Sum(nil)), nil
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +103 to +118
// GetApiAzureFullName returns the name of the Azure resource.
func (a *Api) GetApiAzureFullName() string {
return fmt.Sprintf("%s-%s", a.Namespace, a.Name)
}

// ToAzureApiVersionSet returns an APIVersionSetContract object.
func (a *Api) ToAzureApiVersionSet() apim.APIVersionSetContract {
return apim.APIVersionSetContract{
Properties: &apim.APIVersionSetContractProperties{
DisplayName: &a.Spec.DisplayName,
VersioningScheme: a.Spec.VersioningScheme.AzureAPIVersionScheme(),
Description: a.Spec.Description,
},
Name: utils.ToPointer(a.GetApiAzureFullName()),
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle possible nil values in methods

In the methods ToAzureApiVersionSet and GetApiAzureFullName, ensure that all accessed fields are properly checked for nil to prevent runtime panics.

Review and update the methods to include nil checks where necessary.

Comment on lines +35 to +60
// DisplayName - The display name of the API. This name is used by the developer portal as the API name.
// +kubebuilder:validation:Required
DisplayName string `json:"displayName,omitempty"`
// Description - Description of the API. May include its purpose, where to get more information, and other relevant information.
// +kubebuilder:validation:Optional
Description *string `json:"description,omitempty"`
// VersioningScheme - Indicates the versioning scheme used for the API. Possible values include, but are not limited to, "Segment", "Query", "Header". Default value is "Segment".
// +kubebuilder:validation:Optional
// +kubebuilder:default:="Segment"
// +kubebuilder:validation:Enum:=Header;Query;Segment
VersioningScheme APIVersionScheme `json:"versioningScheme,omitempty"`
// Path - API prefix. The value is combined with the API version to form the URL of the API endpoint.
// +kubebuilder:validation:Required
Path string `json:"path,omitempty"`
// ApiType - Type of API.
// +kubebuilder:validation:Optional
// +kubebuilder:default:="http"
// +default:value:"http"
// +kubebuilder:validation:Enum:=graphql;http;websocket
ApiType *APIType `json:"apiType,omitempty"`
// Contact - Contact details of the API owner.
// +kubebuilder:validation:Optional
Contact *APIContactInformation `json:"contact,omitempty"`
// Versions - A list of API versions associated with the API. If the API is specified using the OpenAPI definition, then the API version is set by the version field of the OpenAPI definition.
// +kubebuilder:validation:Required
Versions []ApiVersionSubSpec `json:"versions,omitempty"`
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure required fields are validated appropriately

The new fields added to ApiSpec include proper validation annotations. However, consider the following:

  • Fields marked with +kubebuilder:validation:Required should omit the omitempty tag in the JSON struct tag to enforce validation.

Apply this diff to ensure required fields are correctly validated:

-	DisplayName string `json:"displayName,omitempty"`
+	DisplayName string `json:"displayName"`

Repeat this change for other required fields like Path and Versions.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +40 to +52
- name: v2
displayName: Version 2
description: First version of the API
serviceUrl: https://example.com/v2
products:
- product1
- product2
protocols:
- https
subscriptionRequired: true
contentFormat: swagger-link-json
content: https://primary-test-aca-vga.gentleground-884783d5.norwayeast.azurecontainerapps.io/swagger/doc.json
isCurrent: true
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix inconsistencies in API version configuration

Several issues in the API version configuration need attention:

  1. Version 2 description is incorrect (states "First version of the API")
  2. Both versions are marked as isCurrent: true, which could cause confusion
  3. The content URL appears to be an environment-specific URL
     - name: v2
       displayName: Version 2
-      description: First version of the API
+      description: Second version of the API
       serviceUrl: https://example.com/v2
       products:
         - product1
         - product2
       protocols:
         - https
       subscriptionRequired: true
       contentFormat: swagger-link-json
-      content: https://primary-test-aca-vga.gentleground-884783d5.norwayeast.azurecontainerapps.io/swagger/doc.json
+      content: https://example.com/v2/swagger/doc.json
-      isCurrent: true
+      isCurrent: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: v2
displayName: Version 2
description: First version of the API
serviceUrl: https://example.com/v2
products:
- product1
- product2
protocols:
- https
subscriptionRequired: true
contentFormat: swagger-link-json
content: https://primary-test-aca-vga.gentleground-884783d5.norwayeast.azurecontainerapps.io/swagger/doc.json
isCurrent: true
- name: v2
displayName: Version 2
description: Second version of the API
serviceUrl: https://example.com/v2
products:
- product1
- product2
protocols:
- https
subscriptionRequired: true
contentFormat: swagger-link-json
content: https://example.com/v2/swagger/doc.json
isCurrent: false
🧰 Tools
🪛 yamllint (1.35.1)

[error] 52-52: no new line character at the end of file

(new-line-at-end-of-file)

Comment on lines +121 to +141
type MockPoller[T any] struct {
IsDone bool
PollResult http.Response
PollError error
FakeResultError error
}

func (p MockPoller[T]) Poll(_ context.Context) (*http.Response, error) {
return &p.PollResult, p.PollError
}

func (p MockPoller[T]) Done() bool {
return p.IsDone
}

func (p MockPoller[T]) Result(_ context.Context, _ *T) error {
if p.FakeResultError != nil {
return p.FakeResultError
}
return nil
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance MockPoller implementation.

The current MockPoller implementation could be more robust:

 type MockPoller[T any] struct {
 	IsDone          bool
 	PollResult      http.Response
 	PollError       error
 	FakeResultError error
+	// Add fields for more complex scenarios
+	PollCount       int    // Track number of polls
+	MaxPolls        int    // Maximum number of polls before success
+	StateSequence   []int  // Sequence of status codes to return
 }

 func (p MockPoller[T]) Poll(_ context.Context) (*http.Response, error) {
-	return &p.PollResult, p.PollError
+	if p.StateSequence != nil && p.PollCount < len(p.StateSequence) {
+		result := http.Response{StatusCode: p.StateSequence[p.PollCount]}
+		p.PollCount++
+		return &result, nil
+	}
+	if p.MaxPolls > 0 && p.PollCount >= p.MaxPolls {
+		return &p.PollResult, p.PollError
+	}
+	p.PollCount++
+	return &p.PollResult, p.PollError
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type MockPoller[T any] struct {
IsDone bool
PollResult http.Response
PollError error
FakeResultError error
}
func (p MockPoller[T]) Poll(_ context.Context) (*http.Response, error) {
return &p.PollResult, p.PollError
}
func (p MockPoller[T]) Done() bool {
return p.IsDone
}
func (p MockPoller[T]) Result(_ context.Context, _ *T) error {
if p.FakeResultError != nil {
return p.FakeResultError
}
return nil
}
type MockPoller[T any] struct {
IsDone bool
PollResult http.Response
PollError error
FakeResultError error
// Add fields for more complex scenarios
PollCount int // Track number of polls
MaxPolls int // Maximum number of polls before success
StateSequence []int // Sequence of status codes to return
}
func (p MockPoller[T]) Poll(_ context.Context) (*http.Response, error) {
if p.StateSequence != nil && p.PollCount < len(p.StateSequence) {
result := http.Response{StatusCode: p.StateSequence[p.PollCount]}
p.PollCount++
return &result, nil
}
if p.MaxPolls > 0 && p.PollCount >= p.MaxPolls {
return &p.PollResult, p.PollError
}
p.PollCount++
return &p.PollResult, p.PollError
}
func (p MockPoller[T]) Done() bool {
return p.IsDone
}
func (p MockPoller[T]) Result(_ context.Context, _ *T) error {
if p.FakeResultError != nil {
return p.FakeResultError
}
return nil
}

Comment on lines +76 to +77
// +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.provisioningState`
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix JSONPath syntax in print column definition.

The JSONPath for the State column uses backticks instead of single quotes, which might cause issues:

-// +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.provisioningState`
+// +kubebuilder:printcolumn:name="State",type=string,JSONPath='.status.provisioningState'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.provisioningState`
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="State",type=string,JSONPath='.status.provisioningState'
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"

@@ -32,7 +32,7 @@ import (
apimv1alpha1 "github.com/Altinn/altinn-platform/services/dis-apim-operator/api/v1alpha1"
)

const BACKEND_FINALIZER = "finalizers.apim.dis.altinn.cloud/backend"
const BACKEND_FINALIZER = "backend.apim.dis.altinn.cloud/finalzer"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix typo in finalizer name.

The finalizer constant contains a typo in the word "finalzer":

-const BACKEND_FINALIZER = "backend.apim.dis.altinn.cloud/finalzer"
+const BACKEND_FINALIZER = "backend.apim.dis.altinn.cloud/finalizer"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const BACKEND_FINALIZER = "backend.apim.dis.altinn.cloud/finalzer"
const BACKEND_FINALIZER = "backend.apim.dis.altinn.cloud/finalizer"

Comment on lines +185 to +201
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
NewClient: azure.NewAPIMClient,
ApimClientConfig: &azure.ApimClientConfig{
AzureConfig: *operatorConfig,
},
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Api")
os.Exit(1)
}
if err = (&controller.ApiVersionReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
NewClient: azure.NewAPIMClient,
ApimClientConfig: &azure.ApimClientConfig{
AzureConfig: *operatorConfig,
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for APIM client initialization

Consider adding validation and error handling for the APIM client configuration before initializing the controllers.

+func validateApimConfig(config *azure.ApimClientConfig) error {
+    if config == nil || config.AzureConfig == nil {
+        return fmt.Errorf("invalid APIM client configuration")
+    }
+    return nil
+}

 if err = (&controller.ApiReconciler{
     Client:    mgr.GetClient(),
     Scheme:    mgr.GetScheme(),
     NewClient: azure.NewAPIMClient,
     ApimClientConfig: &azure.ApimClientConfig{
         AzureConfig: *operatorConfig,
     },
+}).SetupWithManager(mgr); err != nil {
-}).SetupWithManager(mgr); err != nil {
+    if err := validateApimConfig(apimClientConfig); err != nil {
+        setupLog.Error(err, "invalid APIM configuration")
+        os.Exit(1)
+    }
     setupLog.Error(err, "unable to create controller", "controller", "Api")
     os.Exit(1)
 }

Committable suggestion skipped: line range outside the PR's diff.

@tjololo tjololo changed the title Feat/1019 apim api crds feat(apim): crds and controller for configuring apis in apim Dec 13, 2024
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.

1 participant