-
Notifications
You must be signed in to change notification settings - Fork 2
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
base: main
Are you sure you want to change the base?
Changes from all commits
8f877cb
f4a63f3
0a8fd4f
c9789e6
2a986db
1930d1f
f419394
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,3 +25,5 @@ go.work | |
*.swp | ||
*.swo | ||
*~ | ||
|
||
*.ignore.* |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,10 @@ limitations under the License. | |
package v1alpha1 | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/Altinn/altinn-platform/services/dis-apim-operator/internal/utils" | ||
apim "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement/v2" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
|
@@ -28,18 +32,51 @@ type ApiSpec struct { | |
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster | ||
// Important: Run "make" to regenerate code after modifying this file | ||
|
||
// Foo is an example field of Api. Edit api_types.go to remove/update | ||
Foo string `json:"foo,omitempty"` | ||
// 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"` | ||
} | ||
|
||
// ApiStatus defines the observed state of Api. | ||
type ApiStatus struct { | ||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster | ||
// Important: Run "make" to regenerate code after modifying this file | ||
// ProvisioningState - The provisioning state of the API. Possible values are: Provisioning, Succeeded, Failed, Updating, Deleting, and Deleted. | ||
// +kubebuilder:validation:Optional | ||
ProvisioningState ProvisioningState `json:"provisioningState,omitempty"` | ||
// ApiVersionSetID - The identifier of the API Version Set. | ||
// +kubebuilder:validation:Optional | ||
ApiVersionSetID string `json:"apiVersionSetID,omitempty"` | ||
// VersionStates - A list of API Version deployed in the API Management service and the current state of the API Version. | ||
// +kubebuilder:validation:Optional | ||
VersionStates map[string]ApiVersionStatus `json:"versionStates,omitempty"` | ||
} | ||
|
||
// +kubebuilder:object:root=true | ||
// +kubebuilder:subresource:status | ||
// +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.provisioningState` | ||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" | ||
|
||
// Api is the Schema for the apis API. | ||
type Api struct { | ||
|
@@ -62,3 +99,44 @@ type ApiList struct { | |
func init() { | ||
SchemeBuilder.Register(&Api{}, &ApiList{}) | ||
} | ||
|
||
// GetApiAzureFullName returns the name of the Azure resource. | ||
func (a *Api) GetApiAzureFullName() string { | ||
return fmt.Sprintf("%s-%s", a.Namespace, a.Name) | ||
} | ||
Comment on lines
+102
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Based on the analysis of the codebase, I can now generate the final response: Azure resource name validation needed for API names The
Add validation to ensure generated names comply with Azure's requirements:
🔗 Analysis chainMethod Ensure that the namespace and name do not contain characters that could lead to invalid Azure resource names. Run the following script to check for invalid characters: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Verify that the namespace and name contain only valid characters.
# Expect: No output if all names are valid.
# Fetch all Api resources and check for invalid characters
kubectl get apis -A -o json |
jq -r '.items[] | "\(.metadata.namespace)-\(.metadata.name)"' |
grep -E '[^a-zA-Z0-9_\-]'
Length of output: 219 Script: #!/bin/bash
# Let's analyze the codebase to understand the validation and usage of API names
# Check for any validation logic for API names
rg -A 5 "GetApiAzureFullName|validateApiName" --type go
# Look for any Azure resource name validation or sanitization
rg -A 5 "Azure.*Name|resource.*name.*valid" --type go
# Check for any tests related to API naming
fd ".*_test.go" --exec rg -A 5 "GetApiAzureFullName|TestApi.*Name" {}
Length of output: 22440 Script: #!/bin/bash
# Let's check Azure API documentation or validation logic in the codebase
# Look for Azure API name validation patterns or constants
rg -A 5 "Azure.*name.*pattern|Azure.*name.*validation|Azure.*resource.*name" --type go
# Check for any Azure SDK imports or documentation references
rg -B 2 "azure-sdk-for-go|github.com/Azure" --type go
# Look for any validation functions or error handling related to resource names
ast-grep --pattern 'func $$$validate$$$($$$) $$$'
Length of output: 5655 |
||
|
||
// 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()), | ||
} | ||
} | ||
Comment on lines
+103
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle possible nil values in methods In the methods Review and update the methods to include nil checks where necessary. |
||
|
||
// ToApiVersions returns a map of ApiVersion type. | ||
func (a *Api) ToApiVersions() map[string]ApiVersion { | ||
apiVersions := make(map[string]ApiVersion) | ||
for _, version := range a.Spec.Versions { | ||
versionFullName := version.GetApiVersionFullName(a.Name) | ||
apiVersion := ApiVersion{ | ||
TypeMeta: metav1.TypeMeta{}, | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: versionFullName, | ||
Namespace: a.Namespace, | ||
}, | ||
Spec: ApiVersionSpec{ | ||
ApiVersionSetId: a.Status.ApiVersionSetID, | ||
ApiVersionScheme: a.Spec.VersioningScheme, | ||
Path: a.Spec.Path, | ||
ApiType: a.Spec.ApiType, | ||
ApiVersionSubSpec: version, | ||
}, | ||
} | ||
apiVersions[version.GetApiVersionSpecifier()] = apiVersion | ||
} | ||
return apiVersions | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
package v1alpha1 | ||
|
||
import ( | ||
"github.com/Altinn/altinn-platform/services/dis-apim-operator/internal/utils" | ||
apim "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement/v2" | ||
) | ||
|
||
// INSERT ADDITIONAL TYPES | ||
// Important: Run "make" to regenerate code after modifying this file | ||
|
||
// ContentFormat - Format of the Content in which the API is getting imported. | ||
type ContentFormat string | ||
|
||
const ( | ||
// ContentFormatGraphqlLink - The GraphQL API endpoint hosted on a publicly accessible internet address. | ||
ContentFormatGraphqlLink ContentFormat = "graphql-link" | ||
// ContentFormatOpenapi - The contents are inline and Content Type is a OpenAPI 3.0 YAML Document. | ||
ContentFormatOpenapi ContentFormat = "openapi" | ||
// ContentFormatOpenapiJSON - The contents are inline and Content Type is a OpenAPI 3.0 JSON Document. | ||
ContentFormatOpenapiJSON ContentFormat = "openapi+json" | ||
// ContentFormatOpenapiJSONLink - The OpenAPI 3.0 JSON document is hosted on a publicly accessible internet address. | ||
ContentFormatOpenapiJSONLink ContentFormat = "openapi+json-link" | ||
// ContentFormatOpenapiLink - The OpenAPI 3.0 YAML document is hosted on a publicly accessible internet address. | ||
ContentFormatOpenapiLink ContentFormat = "openapi-link" | ||
// ContentFormatSwaggerJSON - The contents are inline and Content Type is a OpenAPI 2.0 JSON Document. | ||
ContentFormatSwaggerJSON ContentFormat = "swagger-json" | ||
// ContentFormatSwaggerLinkJSON - The OpenAPI 2.0 JSON document is hosted on a publicly accessible internet address. | ||
ContentFormatSwaggerLinkJSON ContentFormat = "swagger-link-json" | ||
// ContentFormatWadlLinkJSON - The WADL document is hosted on a publicly accessible internet address. | ||
ContentFormatWadlLinkJSON ContentFormat = "wadl-link-json" | ||
// ContentFormatWadlXML - The contents are inline and Content type is a WADL document. | ||
ContentFormatWadlXML ContentFormat = "wadl-xml" | ||
) | ||
|
||
func (c ContentFormat) AzureContentFormat() *apim.ContentFormat { | ||
contentFormat := apim.ContentFormat(c) | ||
return &contentFormat | ||
} | ||
|
||
type APIContactInformation struct { | ||
// The email address of the contact person/organization. MUST be in the format of an email address | ||
Email *string `json:"email,omitempty"` | ||
|
||
// The identifying name of the contact person/organization | ||
Name *string `json:"name,omitempty"` | ||
|
||
// The URL pointing to the contact information. MUST be in the format of a URL | ||
URL *string `json:"url,omitempty"` | ||
} | ||
|
||
func (a *APIContactInformation) AzureAPIContactInformation() *apim.APIContactInformation { | ||
if a == nil { | ||
return nil | ||
} | ||
return &apim.APIContactInformation{ | ||
Email: a.Email, | ||
Name: a.Name, | ||
URL: a.URL, | ||
} | ||
} | ||
|
||
type APIVersionScheme string | ||
|
||
const ( | ||
// APIVersionSetContractDetailsVersioningSchemeHeader - The API Version is passed in a HTTP header. | ||
APIVersionSetContractDetailsVersioningSchemeHeader APIVersionScheme = "Header" | ||
// APIVersionSetContractDetailsVersioningSchemeQuery - The API Version is passed in a query parameter. | ||
APIVersionSetContractDetailsVersioningSchemeQuery APIVersionScheme = "Query" | ||
// APIVersionSetContractDetailsVersioningSchemeSegment - The API Version is passed in a path segment. | ||
APIVersionSetContractDetailsVersioningSchemeSegment APIVersionScheme = "Segment" | ||
) | ||
|
||
func (a *APIVersionScheme) AzureAPIVersionScheme() *apim.VersioningScheme { | ||
if a == nil { | ||
return nil | ||
} | ||
apiVersionScheme := apim.VersioningScheme(*a) | ||
return &apiVersionScheme | ||
} | ||
|
||
func (a *APIVersionScheme) AzureAPIVersionSetContractDetailsVersioningScheme() *apim.APIVersionSetContractDetailsVersioningScheme { | ||
if a == nil { | ||
return nil | ||
} | ||
apiVersionScheme := apim.APIVersionSetContractDetailsVersioningScheme(*a) | ||
return &apiVersionScheme | ||
} | ||
|
||
type Protocol string | ||
|
||
const ( | ||
ProtocolHTTP Protocol = "http" | ||
ProtocolHTTPS Protocol = "https" | ||
ProtocolWs Protocol = "ws" | ||
ProtocolWss Protocol = "wss" | ||
) | ||
|
||
func (p *Protocol) AzureProtocol() *apim.Protocol { | ||
if p == nil { | ||
return nil | ||
} | ||
protocol := apim.Protocol(*p) | ||
return &protocol | ||
} | ||
|
||
func ToApimProtocolSlice(protocols []Protocol) []*apim.Protocol { | ||
apimProtocols := make([]*apim.Protocol, len(protocols)) | ||
for i, protocol := range protocols { | ||
apimProtocols[i] = utils.ToPointer(apim.Protocol(protocol)) | ||
} | ||
return apimProtocols | ||
} | ||
|
||
type PolicyFormat string | ||
|
||
const ( | ||
// PolicyContentFormatRawxml - The contents are inline and Content type is a non XML encoded policy document. | ||
PolicyContentFormatRawxml PolicyFormat = "rawxml" | ||
// PolicyContentFormatRawxmlLink - The policy document is not XML encoded and is hosted on a HTTP endpoint accessible from | ||
// the API Management service. | ||
PolicyContentFormatRawxmlLink PolicyFormat = "rawxml-link" | ||
// PolicyContentFormatXML - The contents are inline and Content type is an XML document. | ||
PolicyContentFormatXML PolicyFormat = "xml" | ||
// PolicyContentFormatXMLLink - The policy XML document is hosted on a HTTP endpoint accessible from the API Management service. | ||
PolicyContentFormatXMLLink PolicyFormat = "xml-link" | ||
) | ||
|
||
func (p *PolicyFormat) AzurePolicyFormat() *apim.PolicyContentFormat { | ||
if p == nil { | ||
return nil | ||
} | ||
policyFormat := apim.PolicyContentFormat(*p) | ||
return &policyFormat | ||
} | ||
|
||
// APIType - Type of API. | ||
type APIType string | ||
|
||
const ( | ||
APITypeGraphql APIType = "graphql" | ||
APITypeHTTP APIType = "http" | ||
APITypeWebsocket APIType = "websocket" | ||
) | ||
|
||
func (a APIType) AzureApiType() *apim.APIType { | ||
apiType := apim.APIType(a) | ||
return &apiType | ||
} | ||
|
||
type ProvisioningState string | ||
|
||
const ( | ||
ProvisioningStateSucceeded ProvisioningState = "Succeeded" | ||
ProvisioningStateFailed ProvisioningState = "Failed" | ||
ProvisioningStateUpdating ProvisioningState = "Updating" | ||
ProvisioningStateDeleting ProvisioningState = "Deleting" | ||
) |
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
Ensure required fields are validated appropriately
The new fields added to
ApiSpec
include proper validation annotations. However, consider the following:+kubebuilder:validation:Required
should omit theomitempty
tag in the JSON struct tag to enforce validation.Apply this diff to ensure required fields are correctly validated:
Repeat this change for other required fields like
Path
andVersions
.