Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .ci/local_integration_test
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ declare TARGET_CLUSTER
declare CONTROL_CLUSTER
declare GARDEN_CORE_NAMESPACE
declare CONTROL_KUBECONFIG
DEFAULT_MCM_REPO_PATH=$(realpath "$(pwd)/../machine-controller-manager")
declare GNA_SECRET_NAME
DEFAULT_MCM_REPO_PATH=$(realpath "$(pwd)/../machine-controller-manager")


Expand Down Expand Up @@ -237,6 +237,13 @@ fi

set -o allexport
source .env
printf "\e[33mFetching the gardener-node-agent secret name. (If gardener-node-agent authorizer webhook is enabled, then this value is compulsory. Link to PR:https://github.com/gardener/gardener/pull/10535. The value can be found in machineClass.providerSpec.tags/labels. \e[0m\n"
GNA_SECRET_NAME=$(kubectl --kubeconfig=$CONTROL_KUBECONFIG get mcc -n $CONTROL_CLUSTER_NAMESPACE -o jsonpath='{.items[0].providerSpec.tags.worker\.gardener\.cloud_gardener-node-agent-secret-name}')
if [ -z "GNA_SECRET_NAME" ]
then
printf "\e[31m GNA Secret name is empty\e[0m\n"
fi
export GNA_SECRET_NAME=$GNA_SECRET_NAME
set +o allexport

CREDENTIALS_SECRET_NAME=shoot-operator-az-team
Expand Down
32 changes: 31 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
target: ${{ matrix.args.target }}
oci-registry: ${{ needs.prepare.outputs.oci-registry }}
oci-repository: ${{ matrix.args.oci-repository }}
oci-platforms: linux/amd64
oci-platforms: linux/amd64,linux/arm64
ocm-labels: ${{ toJSON(matrix.args.ocm-labels) }}
extra-tags: latest

Expand All @@ -63,3 +63,33 @@ jobs:
linter: gosec
run: .ci/check
go-version: '1.23.3'

test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: gardener/cc-utils/.github/actions/trusted-checkout@master
- uses: actions/setup-go@v5
with:
go-version: '1.23.3'
- name: run-tests
run: |
set -euo pipefail
mkdir /tmp/blobs.d
PATHINWS=src SKIP_INTEGRATION_TESTS=true .ci/test |& tee /tmp/blobs.d/test-log.txt
tar czf /tmp/blobs.d/test-log.tar.gz -C /tmp/blobs.d test-log.txt
- name: add-unittest-results-to-component-descriptor
uses: gardener/cc-utils/.github/actions/export-ocm-fragments@master
with:
blobs-directory: /tmp/blobs.d
ocm-resources: |
- name: test-results
relation: local
access:
type: localBlob
localReference: test-log.tar.gz
labels:
- name: gardener.cloud/purposes
value:
- test
2 changes: 1 addition & 1 deletion .ocm/base-component.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
componentReferences:
- componentName: github.com/gardener/machine-controller-manager
name: machine-controller-manager
version: v0.60.0
version: v0.60.1
main-source:
labels:
- name: cloud.gardener.cnudie/dso/scanning-hints/source_analysis/v1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ocm:
component_name: github.com/gardener/machine-controller-manager
component_version: v0.60.1
release_notes:
- audience: operator
author:
hostname: github.com
type: githubUser
username: takoverflow
category: bugfix
contents: Added a safeguard to delay deletion of machines that are undergoing a
`Create` Request to prevent orphaning of VMs.
mimetype: text/markdown
reference: '[#1045](https://github.com/gardener/machine-controller-manager/pull/1045)'
type: standard
62 changes: 62 additions & 0 deletions pkg/azure/access/helpers/fake_resourcegraph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0

package helpers

import (
"context"

"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph"
)

// FakeResourceGraphClient is a fake implementation of ResourceGraphClient for testing.
type FakeResourceGraphClient struct {
// Responses is a list of responses to return in sequence for each call to Resources
Responses []armresourcegraph.ClientResourcesResponse
// Errors is a list of errors to return in sequence for each call to Resources
Errors []error
// CallCount tracks how many times Resources has been called
CallCount int
// RecordedRequests stores all query requests made to the client
RecordedRequests []armresourcegraph.QueryRequest
}

// NewFakeResourceGraphClient creates a new FakeResourceGraphClient for testing.
func NewFakeResourceGraphClient() *FakeResourceGraphClient {
return &FakeResourceGraphClient{
Responses: []armresourcegraph.ClientResourcesResponse{},
Errors: []error{},
RecordedRequests: []armresourcegraph.QueryRequest{},
}
}

// Resources implements the ResourceGraphClient interface.
// Returns the next response/error in the sequence based on CallCount.
func (f *FakeResourceGraphClient) Resources(_ context.Context, query armresourcegraph.QueryRequest, _ *armresourcegraph.ClientResourcesOptions) (armresourcegraph.ClientResourcesResponse, error) {
f.RecordedRequests = append(f.RecordedRequests, query)
index := f.CallCount
f.CallCount++

if index < len(f.Errors) && f.Errors[index] != nil {
return armresourcegraph.ClientResourcesResponse{}, f.Errors[index]
}

if index < len(f.Responses) {
return f.Responses[index], nil
}

return armresourcegraph.ClientResourcesResponse{}, nil
}

// AddResponse adds a response to be returned by the fake client.
func (f *FakeResourceGraphClient) AddResponse(response armresourcegraph.ClientResourcesResponse) *FakeResourceGraphClient {
f.Responses = append(f.Responses, response)
return f
}

// AddError adds an error to be returned by the fake client.
func (f *FakeResourceGraphClient) AddError(err error) *FakeResourceGraphClient {
f.Errors = append(f.Errors, err)
return f
}
75 changes: 54 additions & 21 deletions pkg/azure/access/helpers/resourcegraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,49 +12,82 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph"
"github.com/gardener/machine-controller-manager-provider-azure/pkg/azure/access/errors"
"github.com/gardener/machine-controller-manager-provider-azure/pkg/azure/instrument"
"k8s.io/utils/pointer"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"
)

const (
resourceGraphQueryServiceLabel = "resource_graph_query"
)

// ResourceGraphClient is an interface for Azure Resource Graph client operations.
// This allows for easier testing by enabling mock implementations.
type ResourceGraphClient interface {
Resources(ctx context.Context, query armresourcegraph.QueryRequest, options *armresourcegraph.ClientResourcesOptions) (armresourcegraph.ClientResourcesResponse, error)
}

// Azure client implements interface
var _ ResourceGraphClient = (*armresourcegraph.Client)(nil)

// MapperFn maps a row of result (represented as map[string]interface{}) to any type T.
type MapperFn[T any] func(map[string]interface{}) *T

// QueryAndMap fires a resource graph KUSTO query constructing it from queryTemplate and templateArgs.
// The result of the query are then mapped using a mapperFn and the result or an error is returned.
// NOTE: All calls to this Azure API are instrumented as prometheus metric.
func QueryAndMap[T any](ctx context.Context, client *armresourcegraph.Client, subscriptionID string, mapperFn MapperFn[T], queryTemplate string, templateArgs ...any) (results []T, err error) {
func QueryAndMap[T any](ctx context.Context, client ResourceGraphClient, subscriptionID string, mapperFn MapperFn[T], queryTemplate string, templateArgs ...any) (results []T, err error) {
defer instrument.AZAPIMetricRecorderFn(resourceGraphQueryServiceLabel, &err)()

query := fmt.Sprintf(queryTemplate, templateArgs...)
resources, err := client.Resources(ctx,
armresourcegraph.QueryRequest{
var skipToken *string
pageCount := 0

// Continue fetching results while there is a skipToken
for {
queryRequest := armresourcegraph.QueryRequest{
Query: to.Ptr(query),
Options: nil,
Subscriptions: []*string{to.Ptr(subscriptionID)},
}, nil)
}

if err != nil {
errors.LogAzAPIError(err, "ResourceGraphQuery failure to execute Query: %s", query)
return nil, err
}
// Set skipToken in options if present for subsequent pages
if skipToken != nil {
queryRequest.Options = &armresourcegraph.QueryRequestOptions{
SkipToken: skipToken,
}
}

if resources.TotalRecords == pointer.Int64(0) {
return results, nil
}
resources, err := client.Resources(ctx, queryRequest, nil)
if err != nil {
errors.LogAzAPIError(err, "ResourceGraphQuery failure to execute Query: %s, with skipToken: %s", query, ptr.Deref(skipToken, "<nil>"))
return nil, err
}
pageCount++

// resourceResponse.Data is a []interface{}
if objSlice, ok := resources.Data.([]interface{}); ok {
for _, obj := range objSlice {
// Each obj in resourceResponse.Data is a map[string]Interface{}
rowElements := obj.(map[string]interface{})
result := mapperFn(rowElements)
if result != nil {
results = append(results, *result)
if ptr.Deref(resources.TotalRecords, 0) == 0 {
klog.Infof("Query completed: fetched %d pages, no results retrieved", pageCount)
return results, nil
}

// resourceResponse.Data is a []interface{}
if objSlice, ok := resources.Data.([]interface{}); ok {
for _, obj := range objSlice {
// Each obj in resourceResponse.Data is a map[string]Interface{}
rowElements := obj.(map[string]interface{})
result := mapperFn(rowElements)
if result != nil {
results = append(results, *result)
}
}
}
// Check if there are more pages to fetch and set skipToken for next iteration
if resources.SkipToken == nil || *resources.SkipToken == "" {
break
}
klog.Infof("Fetching next page (page %d) with skipToken: %s", pageCount+1, *resources.SkipToken)
skipToken = resources.SkipToken
}
return

klog.Infof("Query completed: fetched %d pages, total results: %d", pageCount, len(results))
return results, nil
}
Loading
Loading