-
Notifications
You must be signed in to change notification settings - Fork 69
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
Introduce Cozystack API #460
Conversation
Caution Review failedThe pull request is closed. WalkthroughThis pull request introduces significant changes to the CozyStack API, including the addition of API rule violations related to naming conventions and missing types. It also establishes a new module declaration in Changes
Possibly related PRs
Suggested reviewers
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 36
🧹 Outside diff range and nitpick comments (16)
pkg/registry/registry.go (2)
10-14
: Enhance struct documentation.While the struct is well-designed, consider enhancing the documentation to explain:
- The purpose of the
GVK
field- How this implementation interacts with etcd
-// REST implements a RESTStorage for API services against etcd +// REST implements a RESTStorage for API services against etcd. +// It extends genericregistry.Store with GroupVersionKind information +// to support proper type identification in the API machinery. type REST struct {
16-19
: Document parameter usage in GroupVersionKind method.The
containingGV
parameter is not used in the implementation. Please clarify if this is intentional by adding a comment explaining why the parameter is ignored.// Implement the GroupVersionKindProvider interface +// GroupVersionKind returns the group-version-kind associated with this REST storage. +// The containingGV parameter is ignored as this REST storage maintains its own GVK. func (r *REST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {pkg/apis/apps/install/install.go (2)
25-29
: LGTM! Consider enhancing the documentation.The implementation correctly follows the Kubernetes API machinery patterns. The error handling and version priority setting are appropriate for API registration.
Consider expanding the function documentation to provide more context:
-// Install registers the API group and adds types to a scheme +// Install registers the apps API group and adds all of its types to the given scheme. +// This should be called from the scheme builder in the pkg/apis/apps package to ensure +// all apps types are properly registered with the scheme.
1-29
: Consider adding API validation tests.As this is introducing a new API group, consider implementing the following:
- Conversion tests between different API versions (if applicable)
- Defaulting tests to ensure proper initialization of objects
- Validation tests for all custom resources in this API group
- OpenAPI specification validation
This will ensure the API behaves consistently and maintains compatibility as it evolves.
pkg/apis/apps/validation/validation.go (1)
17-22
: Consider implementing a complete validation strategy.As this is part of a new API introduction, consider implementing a comprehensive validation strategy:
- Add admission webhooks for advanced validation scenarios
- Implement separate validation for updates vs creation
- Add status subresource validation
- Consider implementing a ValidatingWebhookConfiguration
This will ensure robust validation across all API operations.
pkg/cmd/server/start_test.go (3)
37-38
: Fix typo in test description.The description contains a grammatical error.
- desc: "same version as than kube binary", + desc: "same version as kube binary",
31-35
: Fix struct field alignment.The struct fields have inconsistent spacing in their alignment.
testCases := []struct { - desc string - appsEmulationVer *version.Version - expectedKubeEmulationVer *version.Version + desc string + appsEmulationVer *version.Version + expectedKubeEmulationVer *version.Version }{
62-68
: Enhance test failure messages.Consider adding more descriptive failure messages to help debug test failures.
- assert.True(t, mappedKubeEmulationVer.EqualTo(tc.expectedKubeEmulationVer)) + assert.True(t, mappedKubeEmulationVer.EqualTo(tc.expectedKubeEmulationVer), + "expected version %v but got %v", tc.expectedKubeEmulationVer, mappedKubeEmulationVer)pkg/apis/apps/v1alpha1/types.go (1)
34-40
: Add field descriptions for better API documentationThe
Version
field lacks documentation. Consider adding a description to clarify:
- The purpose of this version field (is it the API version, application version, etc.?)
- The expected format or constraints
type ApplicationStatus struct { - // Conditions holds the conditions for the Application. - // +optional + // Version represents the current version of the application. + // +optional Version string `json:"version,omitempty"` + // Conditions holds the conditions for the Application. + // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` }pkg/config/config.go (1)
1-80
: Consider architectural improvements for configuration managementThe configuration system could benefit from the following enhancements:
- Add support for configuration hot-reloading using fsnotify
- Include configuration versioning (
apiVersion
field)- Add documentation about the configuration file format
- Provide example configuration files
Consider implementing a configuration manager:
// ConfigManager handles configuration loading and reloading type ConfigManager struct { config *ResourceConfig configPath string onChange func(*ResourceConfig) mu sync.RWMutex } // NewConfigManager creates a new configuration manager with hot reload support func NewConfigManager(path string, onChange func(*ResourceConfig)) (*ConfigManager, error)Would you like me to create a GitHub issue to track these architectural improvements?
api/api-rules/cozystack_api_violation_exceptions.list (1)
1-25
: Architecture Advice: Rethink API validation exceptions approachThe current file contains many exceptions for core Kubernetes types, which indicates a potential architectural issue in how the API is being designed.
Consider these architectural improvements:
Custom Resource Design:
- Design your APIs as proper CustomResourceDefinitions (CRDs)
- Follow Kubernetes API conventions
- Use controller-gen annotations for proper type generation
API Versioning:
- Implement proper API versioning strategy
- Use conversion webhooks for version transitions
- Follow Kubernetes versioning patterns (alpha -> beta -> stable)
Extension Patterns:
- Use aggregated API servers if needed
- Implement custom controllers
- Extend through CRDs rather than modifying core types
Would you like assistance in implementing any of these improvements?
pkg/cmd/server/start.go (2)
102-103
: Ensure configuration file path is configurableThe flag for the configuration file path defaults to
"config.yaml"
. To enhance flexibility, consider setting a more descriptive default or ensuring that the default path aligns with the expected location of the configuration file.Apply this diff to update the default path:
flags.StringVar(&o.ResourceConfigPath, "config", "config.yaml", "Path to the resource configuration file") +// Consider changing "config.yaml" to a more specific default path if necessary
212-215
: Check for potential race conditions in PostStartHookIn the
AddPostStartHookOrDie
function, you start the shared informer factory without checking if it's already running. While this may be acceptable in the current context, consider adding checks or synchronization mechanisms to prevent potential race conditions in the future.Apply this diff to add a comment or check:
server.GenericAPIServer.AddPostStartHookOrDie("start-sample-server-informers", func(context genericapiserver.PostStartHookContext) error { + // Ensure informers are only started once config.GenericConfig.SharedInformerFactory.Start(context.Done()) return nil })
pkg/registry/apps/application/rest.go (3)
650-654
: Enhance error message inConvertToTable
methodThe error message when failing to convert an unstructured object to
Application
is generic. Providing more details can assist in debugging.Include the object's kind and name in the error message:
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &app) if err != nil { log.Printf("Failed to convert Unstructured to Application: %v", err) - return nil, fmt.Errorf("failed to convert Unstructured to Application: %v", err) + return nil, fmt.Errorf("failed to convert Unstructured object (%s/%s) to Application: %v", obj.GetKind(), obj.GetName(), err) }
750-753
: Handle potential negative durations incomputeAge
Using
currentTime.Sub(creationTime)
may result in a negative duration ifcreationTime
is in the future, which can happen due to clock skews.Add a check to ensure the age is not negative:
func computeAge(creationTime, currentTime time.Time) string { ageDuration := currentTime.Sub(creationTime) + if ageDuration < 0 { + ageDuration = 0 + } return duration.HumanDuration(ageDuration) }
645-679
: Error handling inConvertToTable
In the
ConvertToTable
method, if an object does not implement the expected interfaces, it returns anerrNotAcceptable
. However, in some cases, it might be more appropriate to return aMethodNotSupported
error.Evaluate whether a
MethodNotSupported
error provides more clarity and consistency with API conventions.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (8)
go.sum
is excluded by!**/*.sum
pkg/generated/applyconfiguration/apps/v1alpha1/application.go
is excluded by!**/generated/**
pkg/generated/applyconfiguration/apps/v1alpha1/applicationspec.go
is excluded by!**/generated/**
pkg/generated/applyconfiguration/internal/internal.go
is excluded by!**/generated/**
pkg/generated/applyconfiguration/utils.go
is excluded by!**/generated/**
pkg/generated/listers/apps/v1alpha1/application.go
is excluded by!**/generated/**
pkg/generated/listers/apps/v1alpha1/expansion_generated.go
is excluded by!**/generated/**
pkg/generated/openapi/zz_generated.openapi.go
is excluded by!**/generated/**
📒 Files selected for processing (21)
api/api-rules/cozystack_api_violation_exceptions.list
(1 hunks)go.mod
(1 hunks)hack/update-codegen.sh
(1 hunks)pkg/apis/apps/fuzzer/fuzzer.go
(1 hunks)pkg/apis/apps/install/install.go
(1 hunks)pkg/apis/apps/install/roundtrip_test.go
(1 hunks)pkg/apis/apps/register.go
(1 hunks)pkg/apis/apps/v1alpha1/doc.go
(1 hunks)pkg/apis/apps/v1alpha1/register.go
(1 hunks)pkg/apis/apps/v1alpha1/types.go
(1 hunks)pkg/apis/apps/v1alpha1/zz_generated.conversion.go
(1 hunks)pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go
(1 hunks)pkg/apis/apps/v1alpha1/zz_generated.defaults.go
(1 hunks)pkg/apis/apps/validation/validation.go
(1 hunks)pkg/apiserver/apiserver.go
(1 hunks)pkg/apiserver/scheme_test.go
(1 hunks)pkg/cmd/server/start.go
(1 hunks)pkg/cmd/server/start_test.go
(1 hunks)pkg/config/config.go
(1 hunks)pkg/registry/apps/application/rest.go
(1 hunks)pkg/registry/registry.go
(1 hunks)
✅ Files skipped from review due to trivial changes (7)
- go.mod
- hack/update-codegen.sh
- pkg/apis/apps/register.go
- pkg/apis/apps/v1alpha1/doc.go
- pkg/apis/apps/v1alpha1/zz_generated.conversion.go
- pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go
- pkg/apis/apps/v1alpha1/zz_generated.defaults.go
🔇 Additional comments (18)
pkg/registry/registry.go (1)
4-4
: Verify the import path format.
The import path github.com/aenix.io
seems unusual. Should this be github.com/aenix-io
instead? The latter format is more common for GitHub organizations.
pkg/apiserver/scheme_test.go (2)
23-23
: Verify the import path format
The import path github.com/aenix.io/cozystack
seems unusual. Typically, GitHub repository imports use the format github.com/org/repo
. Please verify if this should be github.com/aenix-io/cozystack
instead.
26-28
: 🛠️ Refactor suggestion
Enhance test documentation and structure
While the test implementation follows the standard pattern, consider these improvements:
- Add documentation explaining the purpose of round-trip testing and what types are being tested
- Consider breaking down into sub-tests for different API types if applicable
- Verify that
Scheme
is properly exported from this package
+// TestRoundTripTypes verifies that the API types can be correctly serialized and
+// deserialized through their codec implementations. It ensures that objects retain
+// their semantic meaning after a round-trip conversion.
func TestRoundTripTypes(t *testing.T) {
- roundtrip.RoundTripTestForScheme(t, Scheme, appsfuzzer.Funcs)
+ t.Run("API Types", func(t *testing.T) {
+ roundtrip.RoundTripTestForScheme(t, Scheme, appsfuzzer.Funcs)
+ })
}
pkg/apis/apps/install/roundtrip_test.go (2)
17-24
: LGTM!
The package declaration and imports are well-organized and follow Go best practices.
26-30
: Verify the test dependencies and track protobuf support.
The round-trip test implementation looks good, following standard Kubernetes API testing patterns.
Let's verify the required dependencies are properly defined:
The TODO comment about enabling protobuf generation should be tracked. Would you like me to create a GitHub issue to track this enhancement?
✅ Verification successful
Let me gather more information about the Install
variable since it wasn't found in the previous search.
Dependencies are properly defined and test implementation is correct
The verification confirms that both dependencies are properly defined:
Install
is defined as a function inpkg/apis/apps/install/install.go
that registers the API groupappsfuzzer.Funcs
is defined inpkg/apis/apps/fuzzer/fuzzer.go
The test implementation correctly uses these dependencies for round-trip serialization testing, following Kubernetes API testing patterns.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that Install and fuzzer.Funcs are properly defined
# Check if Install is defined in the package
rg -t go "var Install\s*=" pkg/apis/apps/install/
# Check if Funcs is defined in the fuzzer package
rg -t go "var Funcs\s*=" pkg/apis/apps/fuzzer/
Length of output: 203
Script:
#!/bin/bash
# Let's check the install.go file directly and look for Install definition
rg -t go "Install\s*=|func Install" pkg/apis/apps/install/ -A 3
# Also check for any imports that might be using Install from another package
rg -t go "^import\s+\(" pkg/apis/apps/install/ -A 10
Length of output: 2016
pkg/apis/apps/install/install.go (1)
19-23
: Verify the import path for the internal package.
The import path github.com/aenix.io/cozystack
seems unusual. Typically, GitHub repositories use the format github.com/organization/repo
.
✅ Verification successful
Import path is correctly configured
The import path github.com/aenix.io/cozystack
is correct as verified by:
- The module declaration in
go.mod
- Consistent usage across multiple Go files in the codebase
- Repository references in documentation files (README.md, CONTRIBUTING.md)
- GitHub repository links in build scripts and documentation pointing to aenix-io organization
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the correct import path by checking go.mod and other Go files
# Test 1: Check the module name in go.mod
echo "Checking module name in go.mod:"
cat go.mod | grep "module"
# Test 2: Check for other occurrences of this import path
echo "Checking for other uses of this import path:"
rg "github.com/aenix.io/cozystack"
Length of output: 5675
pkg/apis/apps/fuzzer/fuzzer.go (3)
19-24
: LGTM! Imports are well-structured
The imports are appropriate for the fuzzing functionality, using standard libraries and necessary dependencies.
1-15
: Verify copyright notice and license compliance
The file contains the Kubernetes Authors copyright notice. Please ensure:
- This code is properly attributed if derived from Kubernetes
- The usage complies with Kubernetes' Apache 2.0 license terms
29-31
: Verify fuzzer coverage against ApplicationSpec fields
Let's ensure the fuzzer adequately covers all fields in ApplicationSpec and respects any validation rules.
pkg/apis/apps/validation/validation.go (1)
33-40
: Implement ApplicationSpec validation rules.
The current implementation lacks actual validation rules. Based on Kubernetes patterns, consider validating:
- Required fields
- Field formats and constraints
- Cross-field validation rules
- Resource references if any
Let's check the ApplicationSpec structure:
Would you like me to help design and implement the validation rules once we have the ApplicationSpec structure?
✅ Verification successful
Implement validation for Application fields
Based on the codebase analysis, the Application type has the following fields that need validation:
appVersion
(string)spec
(apiextensionsv1.JSON)
Suggested validation rules:
- Validate
appVersion
if provided (optional field) - Validate
spec
field which is of typeapiextensionsv1.JSON
for valid JSON structure - Consider adding validation for metadata fields following Kubernetes conventions
Example locations requiring attention:
pkg/apis/apps/validation/validation.go
: Implement the TODO section with the above validations
Note: The original review comment was correct in requesting validation implementation. The ApplicationSpec mentioned in the function name is actually for validating the Application type's fields, as there is no separate ApplicationSpec type in the codebase. The validation function should be renamed to ValidateApplication for clarity.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Find ApplicationSpec definition to understand what needs validation
ast-grep --pattern 'type ApplicationSpec struct {
$$$
}'
Length of output: 60
Script:
#!/bin/bash
# Let's try different approaches to find ApplicationSpec definition
# Try finding the file containing ApplicationSpec using ripgrep
rg -l "ApplicationSpec"
# Search for type definition with ripgrep for more context
rg "type ApplicationSpec" -A 10
# Look for files in apps directory
fd . "pkg/apis/apps"
Length of output: 1924
Script:
#!/bin/bash
# Let's check the actual ApplicationSpec definition in the v1alpha1 types
cat pkg/apis/apps/v1alpha1/types.go | rg -A 10 "type ApplicationSpec"
# Also check if there's a non-versioned type definition
cat pkg/apis/apps/types.go 2>/dev/null | rg -A 10 "type ApplicationSpec"
Length of output: 134
Script:
#!/bin/bash
# Let's try to see the entire content of types.go files
cat pkg/apis/apps/v1alpha1/types.go
# Also check the generated apply configuration to understand the structure
cat pkg/generated/applyconfiguration/apps/v1alpha1/applicationspec.go
Length of output: 4313
pkg/cmd/server/start_test.go (1)
1-26
: LGTM! Well-structured package setup.
The file follows Kubernetes project conventions with proper license header and organized imports.
pkg/apis/apps/v1alpha1/types.go (3)
24-32
: LGTM! Follows Kubernetes API conventions
The ApplicationList
type correctly implements the Kubernetes list type pattern with proper type metadata and list metadata fields.
42-50
: LGTM! Proper implementation of condition methods
The GetConditions
and SetConditions
methods correctly implement the Kubernetes condition pattern.
52-64
:
Several issues need attention in the Application type
-
The
AppVersion
field seems to overlap withStatus.Version
. Consider:- Clarifying the distinction between these fields
- Moving all version-related fields to either spec or status
-
The protobuf field numbers have conflicts:
- Both
metadata
andappVersion
use field number 1 - This could cause serialization issues
- Both
-
Using
apiextensionsv1.JSON
for spec suggests an unstructured approach:- Consider defining a proper spec structure if the fields are known
- If flexibility is required, document the expected schema
Fix the protobuf field numbers:
type Application struct {
metav1.TypeMeta `json:",inline"`
- metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
+ metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
- AppVersion string `json:"appVersion,omitempty" protobuf:"bytes,1,opt,name=version"`
+ AppVersion string `json:"appVersion,omitempty" protobuf:"bytes,2,opt,name=version"`
// +optional
- Spec *apiextensionsv1.JSON `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
- Status ApplicationStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
+ Spec *apiextensionsv1.JSON `json:"spec,omitempty" protobuf:"bytes,3,opt,name=spec"`
+ Status ApplicationStatus `json:"status,omitempty" protobuf:"bytes,4,opt,name=status"`
}
Let's verify if there are any schema definitions or documentation for the expected spec structure:
pkg/config/config.go (1)
49-80
:
Enhance configuration loading robustness
The LoadConfig
function needs several improvements for production readiness:
- Add file path validation
- Implement file size limits
- Use structured validation
- Improve error messages with more context
Here's a suggested implementation:
func LoadConfig(path string) (*ResourceConfig, error) {
+ // Validate path
+ if path == "" {
+ return nil, fmt.Errorf("configuration path cannot be empty")
+ }
+
+ // Check if file exists and is regular
+ fileInfo, err := os.Stat(path)
+ if err != nil {
+ return nil, fmt.Errorf("failed to stat config file: %w", err)
+ }
+ if !fileInfo.Mode().IsRegular() {
+ return nil, fmt.Errorf("config path must be a regular file")
+ }
+
+ // Check file size (e.g., 1MB limit)
+ const maxSize = 1 << 20
+ if fileInfo.Size() > maxSize {
+ return nil, fmt.Errorf("config file too large: %d bytes (max %d)", fileInfo.Size(), maxSize)
+ }
+
data, err := os.ReadFile(path)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to read config file: %w", err)
}
var config ResourceConfig
if err := yaml.Unmarshal(data, &config); err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to parse config file: %w", err)
}
- // Validate the configuration.
- for i, res := range config.Resources {
- if res.Application.Kind == "" {
- return nil, fmt.Errorf("resource at index %d has an empty kind", i)
- }
- if res.Application.Plural == "" {
- return nil, fmt.Errorf("resource at index %d has an empty plural", i)
- }
- if res.Release.Prefix == "" {
- return nil, fmt.Errorf("resource at index %d has an empty release prefix", i)
- }
- if res.Release.Chart.Name == "" {
- return nil, fmt.Errorf("resource at index %d has an empty chart name in release", i)
- }
- if res.Release.Chart.SourceRef.Kind == "" || res.Release.Chart.SourceRef.Name == "" || res.Release.Chart.SourceRef.Namespace == "" {
- return nil, fmt.Errorf("resource at index %d has an incomplete sourceRef for chart in release", i)
- }
+ // Use a validation library like "go-playground/validator"
+ if err := validate.Struct(&config); err != nil {
+ return nil, fmt.Errorf("invalid configuration: %w", err)
}
+
return &config, nil
}
Let's check if there are any existing validation utilities in the codebase:
pkg/apiserver/apiserver.go (1)
88-88
: Verify the method call to c.GenericConfig.New()
Ensure that GenericConfig
has a New()
method with the appropriate signature. If GenericConfig
does not have this method or if the parameters are incorrect, it could lead to a compilation error.
Run the following script to confirm the existence and signature of the New()
method:
✅ Verification successful
The method call to c.GenericConfig.New()
is correctly implemented
The code is using the standard Kubernetes API server pattern correctly:
GenericConfig
is of typegenericapiserver.CompletedConfig
(imported from k8s.io/apiserver/pkg/server)- The
New()
method is called with correct parameters:- First parameter: "apps-apiserver" (string identifier)
- Second parameter:
genericapiserver.NewEmptyDelegate()
- The method call is properly handled with error checking
This is a standard pattern used in Kubernetes API server extensions and the implementation follows the expected conventions.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that `GenericConfig` has a `New()` method with the correct signature.
# Search for the `New` method in the genericapiserver package
ast-grep --pattern 'func (*RecommendedConfig) New($_, $_) ($_, $_) { $$$ }' -- 'k8s.io/apiserver/pkg/server/*.go'
Length of output: 195
Script:
#!/bin/bash
# Let's first find the relevant files and then search for the New method
# Search for files containing GenericConfig
rg -l "GenericConfig"
# Then specifically look for the New method definition
rg "func.*New\(" -A 5
Length of output: 856
Script:
#!/bin/bash
# Let's find the type definition of GenericConfig and its imports
rg "type.*GenericConfig" -A 3
# Also check the imports in apiserver.go to see where GenericConfig comes from
rg "^import \(" -A 20 pkg/apiserver/apiserver.go
Length of output: 830
Script:
#!/bin/bash
# Let's check the content of the file to understand the GenericConfig usage
# We can see it's imported from k8s.io/apiserver/pkg/server as genericapiserver
cat pkg/apiserver/apiserver.go | head -n 100
Length of output: 3072
pkg/cmd/server/start.go (2)
182-188
: Confirm feature gates and effective versions are correctly configured
When configuring serverConfig.FeatureGate
and serverConfig.EffectiveVersion
, ensure that the correct components are referenced. Mistakenly referencing the wrong component could lead to incorrect feature gate behavior or versioning issues.
Please verify that utilversionpkg.DefaultKubeComponent
and apiserver.AppsComponentName
are the intended components for FeatureGateFor
and EffectiveVersionFor
, respectively.
130-133
: Verify version emulation mapping logic
In SetEmulationVersionMapping
, you are mapping the Apps component version to the Kubernetes component version using AppsVersionToKubeVersion
. Ensure that this mapping is accurate and that AppsVersionToKubeVersion
correctly translates component versions, especially when new versions are introduced.
Run the following script to check the version mappings:
Please ensure unit tests cover these mappings.
✅ Verification successful
Let me search for the version mapping implementation and tests.
Let me check the implementation and test coverage more thoroughly.
Let me check the test cases for this mapping.
Let me try a different approach to find the test cases.
Version emulation mapping is well-defined and tested
The version mapping implementation in AppsVersionToKubeVersion
is correct and thoroughly tested. The test cases verify:
- Apps v1.2 maps to current Kubernetes version
- Apps v1.1 maps to Kubernetes version-1
- Apps v1.0 maps to Kubernetes version-2
- Higher Apps versions (v1.3+) are capped at current Kubernetes version
- Non-v1.x Apps versions return nil (no mapping)
The implementation matches these test cases by using an offset from v1.2 as the reference point, ensuring proper version alignment between Apps and Kubernetes components.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that Apps versions are correctly mapped to Kubernetes versions.
# Test: List versions and their mappings. Expect: Correct mapping outputs.
apps_versions=("1.0" "1.1" "1.2")
for ver in "${apps_versions[@]}"; do
echo "Mapping Apps version $ver:"
# Simulate the mapping by invoking the function (pseudo-code)
# This requires actual code execution which is not possible here,
# but ensure tests cover edge cases for version mappings.
done
Length of output: 323
Script:
#!/bin/bash
# Find the AppsVersionToKubeVersion mapping definition
rg "AppsVersionToKubeVersion" -A 5
# Look for related test files
fd ".*_test.*" | rg -i "version|apps.*version"
# Find any version mapping tests
rg "TestVersion.*Mapping|TestEmulation.*Version" -A 5
Length of output: 1288
Script:
#!/bin/bash
# Get the full implementation of AppsVersionToKubeVersion
rg -A 15 "func AppsVersionToKubeVersion"
# Get the test cases
rg -B 5 -A 10 "mappedKubeEmulationVer := AppsVersionToKubeVersion"
# Find any version-related constants or variables
rg "DefaultKubeEffectiveVersion|kubeVer|appsVer" -A 2
Length of output: 35928
Script:
#!/bin/bash
# Get the test cases for AppsVersionToKubeVersion
rg -B 10 -A 10 "func Test.*AppsVersionToKubeVersion|TestVersion.*Mapping"
Length of output: 75
Script:
#!/bin/bash
# Search for test cases in start_test.go since we saw the mapping is in start.go
rg -B 10 -A 10 "appsEmulationVer|expectedKubeEmulationVer" pkg/cmd/server/start_test.go
# Also get any version-related test setup
rg "version\." -A 5 pkg/cmd/server/start_test.go
Length of output: 2899
// RESTInPeace creates REST for Application | ||
func RESTInPeace(r *application.REST) rest.Storage { | ||
return r | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider improving function name and implementation.
Several suggestions for improvement:
- Consider a more professional name like
NewApplicationStorage
orToRESTStorage
- Add nil pointer validation
- Enhance documentation to explain the purpose of this type conversion
-// RESTInPeace creates REST for Application
-func RESTInPeace(r *application.REST) rest.Storage {
+// ToRESTStorage converts an application.REST to rest.Storage interface.
+// This adapter is required to use the Application REST implementation
+// with the generic API machinery.
+func ToRESTStorage(r *application.REST) rest.Storage {
+ if r == nil {
+ panic("application REST storage cannot be nil")
+ }
return r
}
📝 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.
// RESTInPeace creates REST for Application | |
func RESTInPeace(r *application.REST) rest.Storage { | |
return r | |
} | |
// ToRESTStorage converts an application.REST to rest.Storage interface. | |
// This adapter is required to use the Application REST implementation | |
// with the generic API machinery. | |
func ToRESTStorage(r *application.REST) rest.Storage { | |
if r == nil { | |
panic("application REST storage cannot be nil") | |
} | |
return r | |
} |
pkg/apiserver/scheme_test.go
Outdated
/* | ||
Copyright 2017 The Kubernetes Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the copyright notice for Cozystack
The current copyright notice is from Kubernetes Authors. Since this is a new file in the Cozystack project, consider updating the copyright notice to reflect the correct ownership.
-Copyright 2017 The Kubernetes Authors.
+Copyright 2024 The Cozystack Authors.
📝 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.
/* | |
Copyright 2017 The Kubernetes Authors. | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, software | |
distributed under the License is distributed on an "AS IS" BASIS, | |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | |
limitations under the License. | |
*/ | |
/* | |
Copyright 2024 The Cozystack Authors. | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, software | |
distributed under the License is distributed on an "AS IS" BASIS, | |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | |
limitations under the License. | |
*/ |
/* | ||
Copyright 2017 The Kubernetes Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the copyright notice to reflect Cozystack ownership.
The current copyright notice incorrectly attributes the code to Kubernetes Authors. Since this is a new file in the Cozystack project, please update the copyright notice accordingly.
-Copyright 2017 The Kubernetes Authors.
+Copyright 2024 The Cozystack Authors.
📝 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.
/* | |
Copyright 2017 The Kubernetes Authors. | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, software | |
distributed under the License is distributed on an "AS IS" BASIS, | |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | |
limitations under the License. | |
*/ | |
/* | |
Copyright 2024 The Cozystack Authors. | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, software | |
distributed under the License is distributed on an "AS IS" BASIS, | |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | |
limitations under the License. | |
*/ |
pkg/apis/apps/install/install.go
Outdated
/* | ||
Copyright 2017 The Kubernetes Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the copyright notice to reflect CozyStack ownership.
The current copyright notice attributes the code to "The Kubernetes Authors". Since this is a new file in the CozyStack project, the copyright notice should be updated to reflect the actual copyright holders.
-Copyright 2017 The Kubernetes Authors.
+Copyright 2024 The CozyStack Authors.
📝 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.
/* | |
Copyright 2017 The Kubernetes Authors. | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, software | |
distributed under the License is distributed on an "AS IS" BASIS, | |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | |
limitations under the License. | |
*/ | |
/* | |
Copyright 2024 The CozyStack Authors. | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, software | |
distributed under the License is distributed on an "AS IS" BASIS, | |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | |
limitations under the License. | |
*/ |
// Funcs returns the fuzzer functions for the apps api group. | ||
var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { | ||
return []interface{}{ | ||
func(s *apps.ApplicationSpec, c fuzz.Continue) { | ||
c.FuzzNoCustom(s) // fuzz self without calling this function again | ||
}, | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider enhancing fuzzer implementation
While the basic implementation is correct, consider these improvements:
- The function comment could be more descriptive about what aspects of ApplicationSpec are being fuzzed
- You might want to add custom fuzzing for specific fields in ApplicationSpec that have business logic constraints
Consider enhancing the implementation like this:
-// Funcs returns the fuzzer functions for the apps api group.
+// Funcs returns the fuzzer functions for the apps api group.
+// It provides fuzzing for ApplicationSpec and ensures that generated data
+// meets the required constraints for valid application specifications.
var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
func(s *apps.ApplicationSpec, c fuzz.Continue) {
- c.FuzzNoCustom(s) // fuzz self without calling this function again
+ c.FuzzNoCustom(s) // fuzz self without calling this function again
+
+ // Add custom fuzzing for fields with specific constraints
+ // Example:
+ // if s.Replicas != nil {
+ // // Ensure reasonable replica counts for testing
+ // *s.Replicas = int32(c.Intn(10))
+ // }
},
}
}
Committable suggestion skipped: line range outside the PR's diff.
// isRelevantHelmRelease checks if the HelmRelease meets the specified criteria. | ||
func (r *REST) isRelevantHelmRelease(event *watch.Event) (bool, error) { | ||
if event.Object == nil { | ||
return false, nil | ||
} | ||
|
||
hr, ok := event.Object.(*unstructured.Unstructured) | ||
if !ok { | ||
return false, fmt.Errorf("expected Unstructured object, got %T", event.Object) | ||
} | ||
|
||
// Filter by Chart Name | ||
chartName, found, err := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "chart") | ||
if err != nil || !found { | ||
log.Printf("HelmRelease %s missing spec.chart.spec.chart field: %v", hr.GetName(), err) | ||
return false, nil | ||
} | ||
if chartName != r.releaseConfig.Chart.Name { | ||
return false, nil | ||
} | ||
|
||
// Filter by SourceRefConfig and Prefix | ||
return r.matchesSourceRefAndPrefix(hr), nil | ||
} | ||
|
||
// matchesSourceRefAndPrefix checks both SourceRefConfig and Prefix criteria. | ||
func (r *REST) matchesSourceRefAndPrefix(hr *unstructured.Unstructured) bool { | ||
sourceRefKind, found, err := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "kind") | ||
if err != nil || !found { | ||
log.Printf("HelmRelease %s missing spec.chart.spec.sourceRef.kind field: %v", hr.GetName(), err) | ||
return false | ||
} | ||
sourceRefName, found, err := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "name") | ||
if err != nil || !found { | ||
log.Printf("HelmRelease %s missing spec.chart.spec.sourceRef.name field: %v", hr.GetName(), err) | ||
return false | ||
} | ||
sourceRefNamespace, found, err := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "sourceRef", "namespace") | ||
if err != nil || !found { | ||
log.Printf("HelmRelease %s missing spec.chart.spec.sourceRef.namespace field: %v", hr.GetName(), err) | ||
return false | ||
} | ||
if sourceRefKind != r.releaseConfig.Chart.SourceRef.Kind || | ||
sourceRefName != r.releaseConfig.Chart.SourceRef.Name || | ||
sourceRefNamespace != r.releaseConfig.Chart.SourceRef.Namespace { | ||
log.Printf("HelmRelease %s sourceRef does not match expected values", hr.GetName()) | ||
return false | ||
} | ||
|
||
// Additional filtering by Prefix | ||
name := hr.GetName() | ||
if !strings.HasPrefix(name, r.releaseConfig.Prefix) { | ||
log.Printf("HelmRelease %s does not have the expected prefix %s", name, r.releaseConfig.Prefix) | ||
return false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error handling in isRelevantHelmRelease
suppresses critical errors
The isRelevantHelmRelease
function logs errors but continues execution by returning false, nil
. This could mask issues that should be surfaced, possibly leading to unexpected behavior.
Modify the function to return errors to the caller for appropriate handling:
func (r *REST) isRelevantHelmRelease(event *watch.Event) (bool, error) {
if event.Object == nil {
return false, nil
}
hr, ok := event.Object.(*unstructured.Unstructured)
if !ok {
return false, fmt.Errorf("expected Unstructured object, got %T", event.Object)
}
// Filter by Chart Name
chartName, found, err := unstructured.NestedString(hr.Object, "spec", "chart", "spec", "chart")
- if err != nil || !found {
- log.Printf("HelmRelease %s missing spec.chart.spec.chart field: %v", hr.GetName(), err)
- return false, nil
- }
+ if err != nil {
+ return false, fmt.Errorf("error accessing chart name in HelmRelease %s: %v", hr.GetName(), err)
+ }
+ if !found {
+ return false, fmt.Errorf("chart name not found in HelmRelease %s", hr.GetName())
+ }
if chartName != r.releaseConfig.Chart.Name {
return false, nil
}
// Filter by SourceRefConfig and Prefix
return r.matchesSourceRefAndPrefix(hr)
}
Committable suggestion skipped: line range outside the PR's diff.
for _, app := range apps { | ||
row := metav1.TableRow{ | ||
Cells: []interface{}{app.GetName(), getReadyStatus(app.Status.Conditions), computeAge(app.GetCreationTimestamp().Time, now), getVersion(app.Status.Version)}, | ||
Object: runtime.RawExtension{Object: &app}, | ||
} | ||
table.Rows = append(table.Rows, row) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential nil dereference in buildTableFromApplications
When iterating over applications, if app.Status.Conditions
is nil
, the getReadyStatus
function may cause a nil pointer dereference.
Ensure that getReadyStatus
can handle a nil
conditions slice:
func getReadyStatus(conditions []metav1.Condition) string {
+ if conditions == nil {
+ return "Unknown"
+ }
for _, condition := range conditions {
if condition.Type == "Ready" {
Alternatively, check for nil
before calling the function:
readyStatus := "Unknown"
+if app.Status.Conditions != nil {
+ readyStatus = getReadyStatus(app.Status.Conditions)
+}
Committable suggestion skipped: line range outside the PR's diff.
func buildLabelSelector(labels map[string]string) string { | ||
var selectors []string | ||
for k, v := range labels { | ||
selectors = append(selectors, fmt.Sprintf("%s=%s", k, v)) | ||
} | ||
return strings.Join(selectors, ",") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential injection risk in buildLabelSelector
The buildLabelSelector
function concatenates label keys and values without sanitization, which could lead to injection if labels are derived from untrusted sources.
Ensure that label keys and values are sanitized or validated before constructing the selector. Since labels are usually controlled, this may not be a significant risk, but it's good practice to handle this:
func buildLabelSelector(labels map[string]string) string {
var selectors []string
for k, v := range labels {
+ // Sanitize the key and value to prevent injection attacks
+ k = sanitizeLabel(k)
+ v = sanitizeLabel(v)
selectors = append(selectors, fmt.Sprintf("%s=%s", k, v))
}
return strings.Join(selectors, ",")
}
Implement the sanitizeLabel
function to strip or escape any unsafe characters.
Committable suggestion skipped: line range outside the PR's diff.
func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { | ||
app, ok := obj.(*appsv1alpha1.Application) | ||
if !ok { | ||
return nil, fmt.Errorf("expected Application object, got %T", obj) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing invocation of createValidation
in Create
method
The createValidation
function is defined as a parameter but is not called in the Create
method. This may lead to creating Application
objects without proper validation, potentially introducing invalid data into the system.
Consider invoking createValidation
after type assertion to ensure the Application
object is valid before proceeding:
func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
app, ok := obj.(*appsv1alpha1.Application)
if !ok {
return nil, fmt.Errorf("expected Application object, got %T", obj)
}
+ if createValidation != nil {
+ if err := createValidation(ctx, app); err != nil {
+ return nil, err
+ }
+ }
helmRelease, err := r.ConvertApplicationToHelmRelease(app)
📝 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.
func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { | |
app, ok := obj.(*appsv1alpha1.Application) | |
if !ok { | |
return nil, fmt.Errorf("expected Application object, got %T", obj) | |
} | |
func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { | |
app, ok := obj.(*appsv1alpha1.Application) | |
if !ok { | |
return nil, fmt.Errorf("expected Application object, got %T", obj) | |
} | |
if createValidation != nil { | |
if err := createValidation(ctx, app); err != nil { | |
return nil, err | |
} | |
} | |
func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { | ||
namespace, err := r.getNamespace(ctx) | ||
if err != nil { | ||
return nil, false, err | ||
} | ||
|
||
log.Printf("Deleting HelmRelease %s in namespace %s", name, namespace) | ||
|
||
err = r.dynamicClient.Resource(helmReleaseGVR).Namespace(namespace).Delete(ctx, r.releaseConfig.Prefix+name, *options) | ||
if err != nil { | ||
log.Printf("Failed to delete HelmRelease %s: %v", name, err) | ||
return nil, false, err | ||
} | ||
|
||
log.Printf("Successfully deleted HelmRelease %s", name) | ||
return nil, true, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delete
method omits deleteValidation
call
In the Delete
method, the deleteValidation
parameter is not utilized. Skipping this validation step might allow deletion of resources that should be preserved based on custom validation logic.
Integrate deleteValidation
to validate the object before deletion:
func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
namespace, err := r.getNamespace(ctx)
if err != nil {
return nil, false, err
}
+ if deleteValidation != nil {
+ obj, err := r.Get(ctx, name, &metav1.GetOptions{})
+ if err != nil {
+ return nil, false, err
+ }
+ if err := deleteValidation(ctx, obj); err != nil {
+ return nil, false, err
+ }
+ }
log.Printf("Deleting HelmRelease %s in namespace %s", name, namespace)
Committable suggestion skipped: line range outside the PR's diff.
Hi, @kvaps! Have you considered keeping this component in a separate project and developing it in parallel? Is this some integral part without which Cozystack will not work? |
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Signed-off-by: Andrei Kvapil kvapss@gmail.com
Summary by CodeRabbit
New Features
Application
resources, enabling CRUD operations with HelmRelease integration.Application
andApplicationSpec
, laying the groundwork for future validation rules.v1alpha1.apps.cozystack.io
, enhancing API group management.Bug Fixes
Tests
Documentation
v1alpha1
API version, including licensing and code generation annotations.