Skip to content

Commit d76c603

Browse files
committed
e2e: app registration cleanup of orphaned app registrations that are expired
1 parent e3d2ca8 commit d76c603

File tree

13 files changed

+469
-4
lines changed

13 files changed

+469
-4
lines changed

internal/graph/graphsdk/graph_base_service_client.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/graph/graphsdk/kiota-lock.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"descriptionHash": "DAC2FA87E0BD82AA2F0BDC3D7EACC6E2A50E16C33B5C67050C1CB898DBB7A8806C7221FF815AF7042586D869CB8C14A6ED3E07FB6F1D13FE2CEB1C70ADFD3B93",
2+
"descriptionHash": "81C12085B29621FDC2F8185AE6C59D08B34A9F9C612EBAF4E291DAE77C74D279E4544A848482CFF058AC2CF6B75E6457AD6120BD6DBB73CF9F4E1BDB4ED9A526",
33
"descriptionLocation": "../../../tooling/kiota/openapi.yaml",
44
"lockFileVersion": "1.0.0",
55
"kiotaVersion": "1.28.0",

internal/graph/graphsdk/me/me_request_builder.go

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/graph/graphsdk/me/owned_objects_graph_application_request_builder.go

Lines changed: 105 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/graph/graphsdk/me/owned_objects_request_builder.go

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/graph/graphsdk/models/odataerrors/inner_error.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/graph/util/applications.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,20 @@ import (
2020
"fmt"
2121
"time"
2222

23+
abstractions "github.com/microsoft/kiota-abstractions-go"
24+
2325
"k8s.io/apimachinery/pkg/util/wait"
2426

2527
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
2628

2729
"github.com/Azure/ARO-HCP/internal/graph/graphsdk/applications"
30+
"github.com/Azure/ARO-HCP/internal/graph/graphsdk/me"
2831
"github.com/Azure/ARO-HCP/internal/graph/graphsdk/models"
2932
"github.com/Azure/ARO-HCP/internal/graph/graphsdk/models/odataerrors"
3033
)
3134

35+
const AppRegistrationPrefix = "aro-hcp-e2e-"
36+
3237
// Application represents a Microsoft Entra application
3338
type Application struct {
3439
ID string `json:"id"`
@@ -171,3 +176,59 @@ func (c *Client) GetApplication(ctx context.Context, appID string) (*Application
171176
DisplayName: *app.GetDisplayName(),
172177
}, nil
173178
}
179+
180+
// ListOwnedExpiredApplications retrieves applications owned by the current service principal
181+
// where all their credentials have expired and a display name starting with the e2e prefix.
182+
// This uses the /me/ownedObjects endpoint to ensure we only return applications we have
183+
// permission to delete.
184+
func (c *Client) ListOwnedExpiredApplications(ctx context.Context) ([]Application, error) {
185+
var apps []Application
186+
187+
headers := abstractions.NewRequestHeaders()
188+
headers.Add("ConsistencyLevel", "eventual")
189+
190+
reqConfig := &me.OwnedObjectsGraphApplicationRequestBuilderGetRequestConfiguration{
191+
Headers: headers,
192+
QueryParameters: &me.OwnedObjectsGraphApplicationRequestBuilderGetQueryParameters{
193+
Filter: to.Ptr(fmt.Sprintf("startsWith(displayName,'%s')", AppRegistrationPrefix)),
194+
Count: to.Ptr(true),
195+
},
196+
}
197+
198+
resp, err := c.graphClient.Me().OwnedObjects().GraphApplication().Get(ctx, reqConfig)
199+
if err != nil {
200+
return nil, fmt.Errorf("list owned applications: %w", err)
201+
}
202+
203+
for resp != nil {
204+
for _, app := range resp.GetValue() {
205+
// Skip app registrations that have credentials not yet expired
206+
skipApp := false
207+
for _, cred := range app.GetPasswordCredentials() {
208+
if cred.GetEndDateTime() != nil && cred.GetEndDateTime().After(time.Now()) {
209+
skipApp = true
210+
break
211+
}
212+
}
213+
if !skipApp {
214+
apps = append(apps, Application{
215+
ID: *app.GetId(),
216+
AppID: *app.GetAppId(),
217+
DisplayName: *app.GetDisplayName(),
218+
})
219+
}
220+
}
221+
222+
nextLink := resp.GetOdataNextLink()
223+
if nextLink == nil || *nextLink == "" {
224+
break
225+
}
226+
227+
resp, err = c.graphClient.Me().OwnedObjects().GraphApplication().WithUrl(*nextLink).Get(ctx, reqConfig)
228+
if err != nil {
229+
return nil, fmt.Errorf("list owned applications (next page): %w", err)
230+
}
231+
}
232+
233+
return apps, nil
234+
}

test/cmd/aro-hcp-tests/cleanup/cmd.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
"github.com/Azure/ARO-HCP/test/pkg/logger"
2525
"github.com/Azure/ARO-HCP/test/util/cleanup"
26+
"github.com/Azure/ARO-HCP/test/util/cleanup/appregistrations"
2627
kustoroleassignments "github.com/Azure/ARO-HCP/test/util/cleanup/kusto-role-assignments"
2728
"github.com/Azure/ARO-HCP/test/util/cleanup/resourcegroups"
2829
)
@@ -40,6 +41,7 @@ func NewCommand() *cobra.Command {
4041
cmd.PersistentFlags().IntVarP(&opt.Verbosity, "verbosity", "v", opt.Verbosity, "Log verbosity level")
4142

4243
cmd.AddCommand(newCleanupResourceGroupsCommand())
44+
cmd.AddCommand(newCleanupAppRegistrationsCommand())
4345
cmd.AddCommand(newDeleteKustoRoleAssignmentsCommand())
4446

4547
cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
@@ -49,6 +51,35 @@ func NewCommand() *cobra.Command {
4951
return cmd
5052
}
5153

54+
func newCleanupAppRegistrationsCommand() *cobra.Command {
55+
cmd := &cobra.Command{
56+
Use: "app-registrations",
57+
Short: "Delete expired e2e app registrations",
58+
SilenceUsage: true,
59+
}
60+
rawOpt := appregistrations.DefaultOptions()
61+
cmd.Flags().BoolVar(&rawOpt.DryRun, "dry-run", rawOpt.DryRun, "Print which app registrations would be deleted without deleting")
62+
63+
cmd.RunE = func(cmd *cobra.Command, args []string) error {
64+
ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt)
65+
defer cancel()
66+
67+
validatedOpt, err := rawOpt.Validate()
68+
if err != nil {
69+
return err
70+
}
71+
72+
completedOpt, err := validatedOpt.Complete(ctx)
73+
if err != nil {
74+
return err
75+
}
76+
77+
return completedOpt.Run(ctx)
78+
}
79+
80+
return cmd
81+
}
82+
5283
func newDeleteKustoRoleAssignmentsCommand() *cobra.Command {
5384
cmd := &cobra.Command{
5485
Use: "kusto-role-assignments",

0 commit comments

Comments
 (0)