Skip to content

Commit

Permalink
feat(apim): crds and controller for configuring apis in apim (#1175)
Browse files Browse the repository at this point in the history
* feat: api and apiversion reconcile logic

Still some work to be done with the tests. Committing to not loose work

* feat: add base64 encoded string of content

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

* rewrite api controller tests

* rewrite all controller tests using manager instead of simulating reconciliation

* remove commented code

* fix linting errors

* add test for long_running_operations

* fixes after coderabbit review

* fixes after coderabbit review

* fixes after coderabbit review

* fixes after coderabbit review

* added utils.go test for status code !200

* fixes after coderabbit review

* replace Fprintln with Fprint

* Update services/dis-apim-operator/internal/controller/apiversion_controller.go

Co-authored-by: Sebastian Duran <[email protected]>

* fix: handle non-not-found policyErr

---------

Co-authored-by: tjololo <[email protected]>
Co-authored-by: Sebastian Duran <[email protected]>
  • Loading branch information
3 people authored Jan 15, 2025
1 parent 0f668fc commit 3a1e7d3
Show file tree
Hide file tree
Showing 35 changed files with 2,862 additions and 672 deletions.
2 changes: 2 additions & 0 deletions services/dis-apim-operator/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ go.work
*.swp
*.swo
*~

*.ignore.*
93 changes: 89 additions & 4 deletions services/dis-apim-operator/api/v1alpha1/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -28,18 +32,52 @@ 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"`
// 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"`
// 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"`
}

// 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: Succeeded, Failed, Updating, Deleting.
// +kubebuilder:validation:Optional
// +kubebuilder:validation:enum:=Succeeded;Failed;Updating;Deleting
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 {
Expand All @@ -62,3 +100,50 @@ type ApiList struct {
func init() {
SchemeBuilder.Register(&Api{}, &ApiList{})
}

// GetApiAzureFullName returns the name of the Azure resource.
func (a *Api) GetApiAzureFullName() string {
if a == nil {
return ""
}
return fmt.Sprintf("%s-%s", a.Namespace, a.Name)
}

// ToAzureApiVersionSet returns an APIVersionSetContract object.
func (a *Api) ToAzureApiVersionSet() apim.APIVersionSetContract {
if a == nil {
return 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()),
}
}

// 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
}
157 changes: 157 additions & 0 deletions services/dis-apim-operator/api/v1alpha1/apiversion_enums.go
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"
)
Loading

0 comments on commit 3a1e7d3

Please sign in to comment.