Merge branch 'master' into issue-26760

This commit is contained in:
Arya Soni 2026-03-12 12:56:23 +01:00 committed by GitHub
commit aa2be31d0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 945 additions and 442 deletions

View file

@ -373,12 +373,12 @@ jobs:
run: |
rm -rf ui/node_modules/argo-ui/node_modules
- name: Get e2e code coverage
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: e2e-code-coverage
path: e2e-code-coverage
- name: Get unit test code coverage
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: test-results
path: test-results

View file

@ -4,7 +4,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:25.10@sha256:4a9232cc47bf99defcc8860ef62
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
# Also used as the image in CI jobs so needs all dependencies
####################################################################################################
FROM docker.io/library/golang:1.26.0@sha256:b39810f6440772ab1ddaf193aa0c2a2bbddebf7a877f127c113b103e48fd8139 AS builder
FROM docker.io/library/golang:1.26.0@sha256:fb612b7831d53a89cbc0aaa7855b69ad7b0caf603715860cf538df854d047b84 AS builder
WORKDIR /tmp
@ -110,7 +110,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
####################################################################################################
# Argo CD Build stage which performs the actual build of Argo CD binaries
####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26.0@sha256:b39810f6440772ab1ddaf193aa0c2a2bbddebf7a877f127c113b103e48fd8139 AS argocd-build
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26.0@sha256:fb612b7831d53a89cbc0aaa7855b69ad7b0caf603715860cf538df854d047b84 AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd

View file

@ -1,4 +1,4 @@
FROM docker.io/library/golang:1.26.0@sha256:b39810f6440772ab1ddaf193aa0c2a2bbddebf7a877f127c113b103e48fd8139
FROM docker.io/library/golang:1.26.0@sha256:fb612b7831d53a89cbc0aaa7855b69ad7b0caf603715860cf538df854d047b84
ENV DEBIAN_FRONTEND=noninteractive

View file

@ -1,6 +1,10 @@
load('ext://restart_process', 'docker_build_with_restart')
load('ext://uibutton', 'cmd_button', 'location')
# tilt version should be >= v0.37.0 for k8s_server_side_apply tilt-dev/tilt#6680
update_settings(
k8s_server_side_apply="true",
)
# add ui button in web ui to run make codegen-local (top nav)
cmd_button(
'make codegen-local',

View file

@ -10,16 +10,19 @@ import (
"slices"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/codecommit"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
rgsatypes "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/codecommit"
codecommittypes "github.com/aws/aws-sdk-go-v2/service/codecommit/types"
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi"
log "github.com/sirupsen/logrus"
"github.com/aws/aws-sdk-go-v2/config"
application "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)
@ -32,16 +35,16 @@ const (
// AWSCodeCommitClient is a lean facade to the codecommitiface.CodeCommitAPI
// it helps to reduce the mockery generated code.
type AWSCodeCommitClient interface {
ListRepositoriesWithContext(aws.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)
GetRepositoryWithContext(aws.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)
ListBranchesWithContext(aws.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)
GetFolderWithContext(aws.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)
ListRepositories(context.Context, *codecommit.ListRepositoriesInput, ...func(*codecommit.Options)) (*codecommit.ListRepositoriesOutput, error)
GetRepository(context.Context, *codecommit.GetRepositoryInput, ...func(*codecommit.Options)) (*codecommit.GetRepositoryOutput, error)
ListBranches(context.Context, *codecommit.ListBranchesInput, ...func(*codecommit.Options)) (*codecommit.ListBranchesOutput, error)
GetFolder(context.Context, *codecommit.GetFolderInput, ...func(*codecommit.Options)) (*codecommit.GetFolderOutput, error)
}
// AWSTaggingClient is a lean facade to the resourcegroupstaggingapiiface.ResourceGroupsTaggingAPIAPI
// it helps to reduce the mockery generated code.
type AWSTaggingClient interface {
GetResourcesWithContext(aws.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)
GetResources(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error)
}
type AWSCodeCommitProvider struct {
@ -73,7 +76,7 @@ func (p *AWSCodeCommitProvider) ListRepos(ctx context.Context, cloneProtocol str
}
for _, repoName := range repoNames {
repo, err := p.codeCommitClient.GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{
repo, err := p.codeCommitClient.GetRepository(ctx, &codecommit.GetRepositoryInput{
RepositoryName: aws.String(repoName),
})
if err != nil {
@ -85,7 +88,7 @@ func (p *AWSCodeCommitProvider) ListRepos(ctx context.Context, cloneProtocol str
log.Warnf("codecommit returned invalid response for repository %s, skipped", repoName)
continue
}
if aws.StringValue(repo.RepositoryMetadata.DefaultBranch) == "" {
if aws.ToString(repo.RepositoryMetadata.DefaultBranch) == "" {
// if a codecommit repo doesn't have default branch, it's uninitialized. not going to bother with it.
log.Warnf("repository %s does not have default branch, skipped", repoName)
continue
@ -94,11 +97,11 @@ func (p *AWSCodeCommitProvider) ListRepos(ctx context.Context, cloneProtocol str
switch cloneProtocol {
// default to SSH if unspecified (i.e. if "").
case "", "ssh":
url = aws.StringValue(repo.RepositoryMetadata.CloneUrlSsh)
url = aws.ToString(repo.RepositoryMetadata.CloneUrlSsh)
case "https":
url = aws.StringValue(repo.RepositoryMetadata.CloneUrlHttp)
url = aws.ToString(repo.RepositoryMetadata.CloneUrlHttp)
case "https-fips":
url, err = getCodeCommitFIPSEndpoint(aws.StringValue(repo.RepositoryMetadata.CloneUrlHttp))
url, err = getCodeCommitFIPSEndpoint(aws.ToString(repo.RepositoryMetadata.CloneUrlHttp))
if err != nil {
return nil, fmt.Errorf("https-fips is provided but repoUrl can't be transformed to FIPS endpoint: %w", err)
}
@ -108,13 +111,13 @@ func (p *AWSCodeCommitProvider) ListRepos(ctx context.Context, cloneProtocol str
repos = append(repos, &Repository{
// there's no "organization" level at codecommit.
// we are just using AWS accountId for now.
Organization: aws.StringValue(repo.RepositoryMetadata.AccountId),
Repository: aws.StringValue(repo.RepositoryMetadata.RepositoryName),
Organization: aws.ToString(repo.RepositoryMetadata.AccountId),
Repository: aws.ToString(repo.RepositoryMetadata.RepositoryName),
URL: url,
Branch: aws.StringValue(repo.RepositoryMetadata.DefaultBranch),
Branch: aws.ToString(repo.RepositoryMetadata.DefaultBranch),
// we could propagate repo tag keys, but without value not sure if it's any useful.
Labels: []string{},
RepositoryId: aws.StringValue(repo.RepositoryMetadata.RepositoryId),
RepositoryId: aws.ToString(repo.RepositoryMetadata.RepositoryId),
})
}
@ -142,13 +145,9 @@ func (p *AWSCodeCommitProvider) RepoHasPath(ctx context.Context, repo *Repositor
FolderPath: aws.String(parentPath),
RepositoryName: aws.String(repo.Repository),
}
output, err := p.codeCommitClient.GetFolderWithContext(ctx, input)
output, err := p.codeCommitClient.GetFolder(ctx, input)
if err != nil {
if hasAwsError(err,
codecommit.ErrCodeRepositoryDoesNotExistException,
codecommit.ErrCodeCommitDoesNotExistException,
codecommit.ErrCodeFolderDoesNotExistException,
) {
if hasAwsError(err) {
return false, nil
}
// unhandled exception, propagate out
@ -157,22 +156,22 @@ func (p *AWSCodeCommitProvider) RepoHasPath(ctx context.Context, repo *Repositor
// anything that matches.
for _, submodule := range output.SubModules {
if basePath == aws.StringValue(submodule.RelativePath) {
if basePath == aws.ToString(submodule.RelativePath) {
return true, nil
}
}
for _, subpath := range output.SubFolders {
if basePath == aws.StringValue(subpath.RelativePath) {
if basePath == aws.ToString(subpath.RelativePath) {
return true, nil
}
}
for _, subpath := range output.Files {
if basePath == aws.StringValue(subpath.RelativePath) {
if basePath == aws.ToString(subpath.RelativePath) {
return true, nil
}
}
for _, subpath := range output.SymbolicLinks {
if basePath == aws.StringValue(subpath.RelativePath) {
if basePath == aws.ToString(subpath.RelativePath) {
return true, nil
}
}
@ -182,7 +181,7 @@ func (p *AWSCodeCommitProvider) RepoHasPath(ctx context.Context, repo *Repositor
func (p *AWSCodeCommitProvider) GetBranches(ctx context.Context, repo *Repository) ([]*Repository, error) {
repos := make([]*Repository, 0)
if !p.allBranches {
output, err := p.codeCommitClient.GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{
output, err := p.codeCommitClient.GetRepository(ctx, &codecommit.GetRepositoryInput{
RepositoryName: aws.String(repo.Repository),
})
if err != nil {
@ -192,7 +191,7 @@ func (p *AWSCodeCommitProvider) GetBranches(ctx context.Context, repo *Repositor
Organization: repo.Organization,
Repository: repo.Repository,
URL: repo.URL,
Branch: aws.StringValue(output.RepositoryMetadata.DefaultBranch),
Branch: aws.ToString(output.RepositoryMetadata.DefaultBranch),
RepositoryId: repo.RepositoryId,
Labels: repo.Labels,
// getting SHA of the branch requires a separate GetBranch call.
@ -204,7 +203,7 @@ func (p *AWSCodeCommitProvider) GetBranches(ctx context.Context, repo *Repositor
RepositoryName: aws.String(repo.Repository),
}
for {
output, err := p.codeCommitClient.ListBranchesWithContext(ctx, input)
output, err := p.codeCommitClient.ListBranches(ctx, input)
if err != nil {
return nil, err
}
@ -213,7 +212,7 @@ func (p *AWSCodeCommitProvider) GetBranches(ctx context.Context, repo *Repositor
Organization: repo.Organization,
Repository: repo.Repository,
URL: repo.URL,
Branch: aws.StringValue(branch),
Branch: branch,
RepositoryId: repo.RepositoryId,
Labels: repo.Labels,
// getting SHA of the branch requires a separate GetBranch call.
@ -222,7 +221,7 @@ func (p *AWSCodeCommitProvider) GetBranches(ctx context.Context, repo *Repositor
})
}
input.NextToken = output.NextToken
if aws.StringValue(output.NextToken) == "" {
if aws.ToString(output.NextToken) == "" {
break
}
}
@ -241,32 +240,32 @@ func (p *AWSCodeCommitProvider) listRepoNames(ctx context.Context) ([]string, er
listReposInput := &codecommit.ListRepositoriesInput{}
var output *codecommit.ListRepositoriesOutput
for {
output, err = p.codeCommitClient.ListRepositoriesWithContext(ctx, listReposInput)
output, err = p.codeCommitClient.ListRepositories(ctx, listReposInput)
if err != nil {
break
}
for _, repo := range output.Repositories {
repoNames = append(repoNames, aws.StringValue(repo.RepositoryName))
repoNames = append(repoNames, aws.ToString(repo.RepositoryName))
}
listReposInput.NextToken = output.NextToken
if aws.StringValue(output.NextToken) == "" {
if aws.ToString(output.NextToken) == "" {
break
}
}
} else {
log.Debugf("tag filer is specified, calling tagging api to list repos")
discoveryInput := &resourcegroupstaggingapi.GetResourcesInput{
ResourceTypeFilters: aws.StringSlice([]string{resourceTypeCodeCommitRepository}),
ResourceTypeFilters: []string{resourceTypeCodeCommitRepository},
TagFilters: tagFilters,
}
var output *resourcegroupstaggingapi.GetResourcesOutput
for {
output, err = p.taggingClient.GetResourcesWithContext(ctx, discoveryInput)
output, err = p.taggingClient.GetResources(ctx, discoveryInput)
if err != nil {
break
}
for _, resource := range output.ResourceTagMappingList {
repoArn := aws.StringValue(resource.ResourceARN)
repoArn := aws.ToString(resource.ResourceARN)
log.Debugf("discovered codecommit repo with arn %s", repoArn)
repoName, extractErr := getCodeCommitRepoName(repoArn)
if extractErr != nil {
@ -276,7 +275,7 @@ func (p *AWSCodeCommitProvider) listRepoNames(ctx context.Context) ([]string, er
repoNames = append(repoNames, repoName)
}
discoveryInput.PaginationToken = output.PaginationToken
if aws.StringValue(output.PaginationToken) == "" {
if aws.ToString(output.PaginationToken) == "" {
break
}
}
@ -284,19 +283,20 @@ func (p *AWSCodeCommitProvider) listRepoNames(ctx context.Context) ([]string, er
return repoNames, err
}
func (p *AWSCodeCommitProvider) getTagFilters() []*resourcegroupstaggingapi.TagFilter {
filters := make(map[string]*resourcegroupstaggingapi.TagFilter)
// getTagFilters filters by tag
func (p *AWSCodeCommitProvider) getTagFilters() []rgsatypes.TagFilter {
filters := make(map[string]rgsatypes.TagFilter)
for _, tagFilter := range p.tagFilters {
filter, hasKey := filters[tagFilter.Key]
if !hasKey {
filter = &resourcegroupstaggingapi.TagFilter{
filter := filters[tagFilter.Key]
if filter.Key == nil {
filter = rgsatypes.TagFilter{
Key: aws.String(tagFilter.Key),
}
filters[tagFilter.Key] = filter
}
if tagFilter.Value != "" {
filter.Values = append(filter.Values, aws.String(tagFilter.Value))
filter.Values = append(filter.Values, tagFilter.Value)
}
filters[tagFilter.Key] = filter
}
return slices.Collect(maps.Values(filters))
}
@ -326,12 +326,15 @@ func getCodeCommitFIPSEndpoint(repoURL string) (string, error) {
return strings.Replace(repoURL, prefixGitURLHTTPS, prefixGitURLHTTPSFIPS, 1), nil
}
func hasAwsError(err error, codes ...string) bool {
var awsErr awserr.Error
if errors.As(err, &awsErr) {
return slices.Contains(codes, awsErr.Code())
}
return false
func hasAwsError(err error) bool {
// Check for common CodeCommit exceptions using SDK v2 typed errors
var repoNotFound *codecommittypes.RepositoryDoesNotExistException
var commitNotFound *codecommittypes.CommitDoesNotExistException
var folderNotFound *codecommittypes.FolderDoesNotExistException
return errors.As(err, &repoNotFound) ||
errors.As(err, &commitNotFound) ||
errors.As(err, &folderNotFound)
}
// toAbsolutePath transforms a path input to absolute path, as required by AWS CodeCommit
@ -343,37 +346,32 @@ func toAbsolutePath(path string) string {
return filepath.ToSlash(filepath.Join("/", path)) //nolint:gocritic // Prepend slash to have an absolute path
}
func createAWSDiscoveryClients(_ context.Context, role string, region string) (*resourcegroupstaggingapi.ResourceGroupsTaggingAPI, *codecommit.CodeCommit, error) {
podSession, err := session.NewSession()
if err != nil {
return nil, nil, fmt.Errorf("error creating new AWS pod session: %w", err)
}
discoverySession := podSession
// assume role if provided - this allows cross account CodeCommit repo discovery.
if role != "" {
log.Debugf("role %s is provided for AWS CodeCommit discovery", role)
assumeRoleCreds := stscreds.NewCredentials(podSession, role)
discoverySession, err = session.NewSession(&aws.Config{
Credentials: assumeRoleCreds,
})
if err != nil {
return nil, nil, fmt.Errorf("error creating new AWS discovery session: %w", err)
}
} else {
log.Debugf("role is not provided for AWS CodeCommit discovery, using pod role")
}
// use region explicitly if provided - this allows cross region CodeCommit repo discovery.
// createAWSDiscoveryClients creates AWS clients
func createAWSDiscoveryClients(ctx context.Context, role string, region string) (*resourcegroupstaggingapi.Client, *codecommit.Client, error) {
var configOpts []func(*config.LoadOptions) error
if region != "" {
log.Debugf("region %s is provided for AWS CodeCommit discovery", region)
discoverySession = discoverySession.Copy(&aws.Config{
Region: aws.String(region),
})
configOpts = append(configOpts, config.WithRegion(region))
} else {
log.Debugf("region is not provided for AWS CodeCommit discovery, using pod region")
}
// Load the default config based on config options
cfg, err := config.LoadDefaultConfig(ctx, configOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error loading default config: %w", err)
}
client := sts.NewFromConfig(cfg)
// assume role if provided - this allows cross account CodeCommit repo discovery.
if role != "" {
log.Debugf("role %s is provided for AWS CodeCommit discovery", role)
assumeRoleCreds := stscreds.NewAssumeRoleProvider(client, role)
cfg.Credentials = aws.NewCredentialsCache(assumeRoleCreds)
} else {
log.Debugf("role is not provided for AWS CodeCommit discovery, using pod role")
}
taggingClient := resourcegroupstaggingapi.New(discoverySession)
codeCommitClient := codecommit.New(discoverySession)
taggingClient := resourcegroupstaggingapi.NewFromConfig(cfg)
codeCommitClient := codecommit.NewFromConfig(cfg)
return taggingClient, codeCommitClient, nil
}

View file

@ -5,10 +5,13 @@ import (
"sort"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/codecommit"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/codecommit"
codecommittypes "github.com/aws/aws-sdk-go-v2/service/codecommit/types"
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi"
rgsatypes "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi/types"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@ -34,7 +37,7 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
repositories []*awsCodeCommitTestRepository
cloneProtocol string
tagFilters []*v1alpha1.TagFilter
expectTagFilters []*resourcegroupstaggingapi.TagFilter
expectTagFilters []rgsatypes.TagFilter
listRepositoryError error
expectOverallError bool
expectListAtCodeCommit bool
@ -57,8 +60,8 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
{Key: "key1", Value: "value2"},
{Key: "key2"},
},
expectTagFilters: []*resourcegroupstaggingapi.TagFilter{
{Key: aws.String("key1"), Values: aws.StringSlice([]string{"value1", "value2"})},
expectTagFilters: []rgsatypes.TagFilter{
{Key: aws.String("key1"), Values: []string{"value1", "value2"}},
{Key: aws.String("key2")},
},
expectOverallError: false,
@ -80,7 +83,7 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
tagFilters: []*v1alpha1.TagFilter{
{Key: "key1"},
},
expectTagFilters: []*resourcegroupstaggingapi.TagFilter{
expectTagFilters: []rgsatypes.TagFilter{
{Key: aws.String("key1")},
},
expectOverallError: false,
@ -160,12 +163,12 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
codeCommitClient := mocks.NewAWSCodeCommitClient(t)
taggingClient := mocks.NewAWSTaggingClient(t)
ctx := t.Context()
codecommitRepoNameIdPairs := make([]*codecommit.RepositoryNameIdPair, 0)
resourceTaggings := make([]*resourcegroupstaggingapi.ResourceTagMapping, 0)
codecommitRepoNameIdPairs := make([]codecommittypes.RepositoryNameIdPair, 0)
resourceTaggings := make([]rgsatypes.ResourceTagMapping, 0)
validRepositories := make([]*awsCodeCommitTestRepository, 0)
for _, repo := range testCase.repositories {
repoMetadata := &codecommit.RepositoryMetadata{
repoMetadata := &codecommittypes.RepositoryMetadata{
AccountId: aws.String(repo.accountId),
Arn: aws.String(repo.arn),
CloneUrlHttp: aws.String("https://git-codecommit.us-east-1.amazonaws.com/v1/repos/" + repo.name),
@ -177,13 +180,13 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
if repo.getRepositoryNilMetadata {
repoMetadata = nil
}
codeCommitClient.EXPECT().GetRepositoryWithContext(mock.Anything, &codecommit.GetRepositoryInput{RepositoryName: aws.String(repo.name)}).
codeCommitClient.EXPECT().GetRepository(mock.Anything, &codecommit.GetRepositoryInput{RepositoryName: aws.String(repo.name)}).
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: repoMetadata}, repo.getRepositoryError).Maybe()
codecommitRepoNameIdPairs = append(codecommitRepoNameIdPairs, &codecommit.RepositoryNameIdPair{
codecommitRepoNameIdPairs = append(codecommitRepoNameIdPairs, codecommittypes.RepositoryNameIdPair{
RepositoryId: aws.String(repo.id),
RepositoryName: aws.String(repo.name),
})
resourceTaggings = append(resourceTaggings, &resourcegroupstaggingapi.ResourceTagMapping{
resourceTaggings = append(resourceTaggings, rgsatypes.ResourceTagMapping{
ResourceARN: aws.String(repo.arn),
})
if repo.valid {
@ -192,14 +195,14 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
}
if testCase.expectListAtCodeCommit {
codeCommitClient.EXPECT().ListRepositoriesWithContext(mock.Anything, &codecommit.ListRepositoriesInput{}).
codeCommitClient.EXPECT().ListRepositories(mock.Anything, &codecommit.ListRepositoriesInput{}).
Return(&codecommit.ListRepositoriesOutput{
Repositories: codecommitRepoNameIdPairs,
}, testCase.listRepositoryError).Maybe()
} else {
taggingClient.EXPECT().GetResourcesWithContext(mock.Anything, mock.MatchedBy(equalIgnoringTagFilterOrder(&resourcegroupstaggingapi.GetResourcesInput{
taggingClient.EXPECT().GetResources(mock.Anything, mock.MatchedBy(equalIgnoringTagFilterOrder(&resourcegroupstaggingapi.GetResourcesInput{
TagFilters: testCase.expectTagFilters,
ResourceTypeFilters: aws.StringSlice([]string{resourceTypeCodeCommitRepository}),
ResourceTypeFilters: []string{resourceTypeCodeCommitRepository},
}))).
Return(&resourcegroupstaggingapi.GetResourcesOutput{
ResourceTagMappingList: resourceTaggings,
@ -249,7 +252,7 @@ func TestAWSCodeCommitRepoHasPath(t *testing.T) {
path: "lib/config.yaml",
expectedGetFolderPath: "/lib",
getFolderOutput: &codecommit.GetFolderOutput{
Files: []*codecommit.File{
Files: []codecommittypes.File{
{RelativePath: aws.String("config.yaml")},
},
},
@ -261,7 +264,7 @@ func TestAWSCodeCommitRepoHasPath(t *testing.T) {
path: "lib/config",
expectedGetFolderPath: "/lib",
getFolderOutput: &codecommit.GetFolderOutput{
SubFolders: []*codecommit.Folder{
SubFolders: []codecommittypes.Folder{
{RelativePath: aws.String("config")},
},
},
@ -273,7 +276,7 @@ func TestAWSCodeCommitRepoHasPath(t *testing.T) {
path: "/lib/submodule/",
expectedGetFolderPath: "/lib",
getFolderOutput: &codecommit.GetFolderOutput{
SubModules: []*codecommit.SubModule{
SubModules: []codecommittypes.SubModule{
{RelativePath: aws.String("submodule")},
},
},
@ -285,7 +288,7 @@ func TestAWSCodeCommitRepoHasPath(t *testing.T) {
path: "./lib/service.json",
expectedGetFolderPath: "/lib",
getFolderOutput: &codecommit.GetFolderOutput{
SymbolicLinks: []*codecommit.SymbolicLink{
SymbolicLinks: []codecommittypes.SymbolicLink{
{RelativePath: aws.String("service.json")},
},
},
@ -297,16 +300,16 @@ func TestAWSCodeCommitRepoHasPath(t *testing.T) {
path: "no-match.json",
expectedGetFolderPath: "/",
getFolderOutput: &codecommit.GetFolderOutput{
Files: []*codecommit.File{
Files: []codecommittypes.File{
{RelativePath: aws.String("config.yaml")},
},
SubFolders: []*codecommit.Folder{
SubFolders: []codecommittypes.Folder{
{RelativePath: aws.String("config")},
},
SubModules: []*codecommit.SubModule{
SubModules: []codecommittypes.SubModule{
{RelativePath: aws.String("submodule")},
},
SymbolicLinks: []*codecommit.SymbolicLink{
SymbolicLinks: []codecommittypes.SymbolicLink{
{RelativePath: aws.String("service.json")},
},
},
@ -317,7 +320,7 @@ func TestAWSCodeCommitRepoHasPath(t *testing.T) {
name: "RepoHasPath when parent folder not found",
path: "lib/submodule",
expectedGetFolderPath: "/lib",
getFolderError: &codecommit.FolderDoesNotExistException{},
getFolderError: &codecommittypes.FolderDoesNotExistException{},
expectOverallError: false,
},
{
@ -347,7 +350,7 @@ func TestAWSCodeCommitRepoHasPath(t *testing.T) {
taggingClient := mocks.NewAWSTaggingClient(t)
ctx := t.Context()
if testCase.expectedGetFolderPath != "" {
codeCommitClient.EXPECT().GetFolderWithContext(mock.Anything, &codecommit.GetFolderInput{
codeCommitClient.EXPECT().GetFolder(mock.Anything, &codecommit.GetFolderInput{
CommitSpecifier: aws.String(branch),
FolderPath: aws.String(testCase.expectedGetFolderPath),
RepositoryName: aws.String(repoName),
@ -419,13 +422,15 @@ func TestAWSCodeCommitGetBranches(t *testing.T) {
taggingClient := mocks.NewAWSTaggingClient(t)
ctx := t.Context()
if testCase.allBranches {
codeCommitClient.EXPECT().ListBranchesWithContext(mock.Anything, &codecommit.ListBranchesInput{
branches := make([]string, len(testCase.branches))
copy(branches, testCase.branches)
codeCommitClient.EXPECT().ListBranches(mock.Anything, &codecommit.ListBranchesInput{
RepositoryName: aws.String(name),
}).
Return(&codecommit.ListBranchesOutput{Branches: aws.StringSlice(testCase.branches)}, testCase.apiError).Maybe()
Return(&codecommit.ListBranchesOutput{Branches: branches}, testCase.apiError).Maybe()
} else {
codeCommitClient.EXPECT().GetRepositoryWithContext(mock.Anything, &codecommit.GetRepositoryInput{RepositoryName: aws.String(name)}).
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: &codecommit.RepositoryMetadata{
codeCommitClient.EXPECT().GetRepository(mock.Anything, &codecommit.GetRepositoryInput{RepositoryName: aws.String(name)}).
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: &codecommittypes.RepositoryMetadata{
AccountId: aws.String(organization),
DefaultBranch: aws.String(defaultBranch),
}}, testCase.apiError).Maybe()
@ -470,8 +475,22 @@ func TestAWSCodeCommitGetBranches(t *testing.T) {
func equalIgnoringTagFilterOrder(expected *resourcegroupstaggingapi.GetResourcesInput) func(*resourcegroupstaggingapi.GetResourcesInput) bool {
return func(actual *resourcegroupstaggingapi.GetResourcesInput) bool {
sort.Slice(actual.TagFilters, func(i, j int) bool {
return *actual.TagFilters[i].Key < *actual.TagFilters[j].Key
keyI := ""
keyJ := ""
if actual.TagFilters[i].Key != nil {
keyI = *actual.TagFilters[i].Key
}
if actual.TagFilters[j].Key != nil {
keyJ = *actual.TagFilters[j].Key
}
return keyI < keyJ
})
return cmp.Equal(expected, actual)
// Ignore unexported fields in AWS SDK v2 types
return cmp.Equal(expected, actual,
cmpopts.IgnoreUnexported(
rgsatypes.TagFilter{},
resourcegroupstaggingapi.GetResourcesInput{},
),
)
}
}

View file

@ -5,9 +5,9 @@
package mocks
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/codecommit"
"context"
"github.com/aws/aws-sdk-go-v2/service/codecommit"
mock "github.com/stretchr/testify/mock"
)
@ -38,71 +38,71 @@ func (_m *AWSCodeCommitClient) EXPECT() *AWSCodeCommitClient_Expecter {
return &AWSCodeCommitClient_Expecter{mock: &_m.Mock}
}
// GetFolderWithContext provides a mock function for the type AWSCodeCommitClient
func (_mock *AWSCodeCommitClient) GetFolderWithContext(v aws.Context, getFolderInput *codecommit.GetFolderInput, options ...request.Option) (*codecommit.GetFolderOutput, error) {
// request.Option
_va := make([]interface{}, len(options))
for _i := range options {
_va[_i] = options[_i]
// GetFolder provides a mock function for the type AWSCodeCommitClient
func (_mock *AWSCodeCommitClient) GetFolder(context1 context.Context, getFolderInput *codecommit.GetFolderInput, fns ...func(*codecommit.Options)) (*codecommit.GetFolderOutput, error) {
// func(*codecommit.Options)
_va := make([]interface{}, len(fns))
for _i := range fns {
_va[_i] = fns[_i]
}
var _ca []interface{}
_ca = append(_ca, v, getFolderInput)
_ca = append(_ca, context1, getFolderInput)
_ca = append(_ca, _va...)
ret := _mock.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetFolderWithContext")
panic("no return value specified for GetFolder")
}
var r0 *codecommit.GetFolderOutput
var r1 error
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)); ok {
return returnFunc(v, getFolderInput, options...)
if returnFunc, ok := ret.Get(0).(func(context.Context, *codecommit.GetFolderInput, ...func(*codecommit.Options)) (*codecommit.GetFolderOutput, error)); ok {
return returnFunc(context1, getFolderInput, fns...)
}
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.GetFolderInput, ...request.Option) *codecommit.GetFolderOutput); ok {
r0 = returnFunc(v, getFolderInput, options...)
if returnFunc, ok := ret.Get(0).(func(context.Context, *codecommit.GetFolderInput, ...func(*codecommit.Options)) *codecommit.GetFolderOutput); ok {
r0 = returnFunc(context1, getFolderInput, fns...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*codecommit.GetFolderOutput)
}
}
if returnFunc, ok := ret.Get(1).(func(aws.Context, *codecommit.GetFolderInput, ...request.Option) error); ok {
r1 = returnFunc(v, getFolderInput, options...)
if returnFunc, ok := ret.Get(1).(func(context.Context, *codecommit.GetFolderInput, ...func(*codecommit.Options)) error); ok {
r1 = returnFunc(context1, getFolderInput, fns...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AWSCodeCommitClient_GetFolderWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFolderWithContext'
type AWSCodeCommitClient_GetFolderWithContext_Call struct {
// AWSCodeCommitClient_GetFolder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFolder'
type AWSCodeCommitClient_GetFolder_Call struct {
*mock.Call
}
// GetFolderWithContext is a helper method to define mock.On call
// - v aws.Context
// GetFolder is a helper method to define mock.On call
// - context1 context.Context
// - getFolderInput *codecommit.GetFolderInput
// - options ...request.Option
func (_e *AWSCodeCommitClient_Expecter) GetFolderWithContext(v interface{}, getFolderInput interface{}, options ...interface{}) *AWSCodeCommitClient_GetFolderWithContext_Call {
return &AWSCodeCommitClient_GetFolderWithContext_Call{Call: _e.mock.On("GetFolderWithContext",
append([]interface{}{v, getFolderInput}, options...)...)}
// - fns ...func(*codecommit.Options)
func (_e *AWSCodeCommitClient_Expecter) GetFolder(context1 interface{}, getFolderInput interface{}, fns ...interface{}) *AWSCodeCommitClient_GetFolder_Call {
return &AWSCodeCommitClient_GetFolder_Call{Call: _e.mock.On("GetFolder",
append([]interface{}{context1, getFolderInput}, fns...)...)}
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Run(run func(v aws.Context, getFolderInput *codecommit.GetFolderInput, options ...request.Option)) *AWSCodeCommitClient_GetFolderWithContext_Call {
func (_c *AWSCodeCommitClient_GetFolder_Call) Run(run func(context1 context.Context, getFolderInput *codecommit.GetFolderInput, fns ...func(*codecommit.Options))) *AWSCodeCommitClient_GetFolder_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 aws.Context
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(aws.Context)
arg0 = args[0].(context.Context)
}
var arg1 *codecommit.GetFolderInput
if args[1] != nil {
arg1 = args[1].(*codecommit.GetFolderInput)
}
var arg2 []request.Option
variadicArgs := make([]request.Option, len(args)-2)
var arg2 []func(*codecommit.Options)
variadicArgs := make([]func(*codecommit.Options), len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
variadicArgs[i] = a.(func(*codecommit.Options))
}
}
arg2 = variadicArgs
@ -115,81 +115,81 @@ func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Run(run func(v aws.Cont
return _c
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Return(getFolderOutput *codecommit.GetFolderOutput, err error) *AWSCodeCommitClient_GetFolderWithContext_Call {
func (_c *AWSCodeCommitClient_GetFolder_Call) Return(getFolderOutput *codecommit.GetFolderOutput, err error) *AWSCodeCommitClient_GetFolder_Call {
_c.Call.Return(getFolderOutput, err)
return _c
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) RunAndReturn(run func(v aws.Context, getFolderInput *codecommit.GetFolderInput, options ...request.Option) (*codecommit.GetFolderOutput, error)) *AWSCodeCommitClient_GetFolderWithContext_Call {
func (_c *AWSCodeCommitClient_GetFolder_Call) RunAndReturn(run func(context1 context.Context, getFolderInput *codecommit.GetFolderInput, fns ...func(*codecommit.Options)) (*codecommit.GetFolderOutput, error)) *AWSCodeCommitClient_GetFolder_Call {
_c.Call.Return(run)
return _c
}
// GetRepositoryWithContext provides a mock function for the type AWSCodeCommitClient
func (_mock *AWSCodeCommitClient) GetRepositoryWithContext(v aws.Context, getRepositoryInput *codecommit.GetRepositoryInput, options ...request.Option) (*codecommit.GetRepositoryOutput, error) {
// request.Option
_va := make([]interface{}, len(options))
for _i := range options {
_va[_i] = options[_i]
// GetRepository provides a mock function for the type AWSCodeCommitClient
func (_mock *AWSCodeCommitClient) GetRepository(context1 context.Context, getRepositoryInput *codecommit.GetRepositoryInput, fns ...func(*codecommit.Options)) (*codecommit.GetRepositoryOutput, error) {
// func(*codecommit.Options)
_va := make([]interface{}, len(fns))
for _i := range fns {
_va[_i] = fns[_i]
}
var _ca []interface{}
_ca = append(_ca, v, getRepositoryInput)
_ca = append(_ca, context1, getRepositoryInput)
_ca = append(_ca, _va...)
ret := _mock.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetRepositoryWithContext")
panic("no return value specified for GetRepository")
}
var r0 *codecommit.GetRepositoryOutput
var r1 error
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)); ok {
return returnFunc(v, getRepositoryInput, options...)
if returnFunc, ok := ret.Get(0).(func(context.Context, *codecommit.GetRepositoryInput, ...func(*codecommit.Options)) (*codecommit.GetRepositoryOutput, error)); ok {
return returnFunc(context1, getRepositoryInput, fns...)
}
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.GetRepositoryInput, ...request.Option) *codecommit.GetRepositoryOutput); ok {
r0 = returnFunc(v, getRepositoryInput, options...)
if returnFunc, ok := ret.Get(0).(func(context.Context, *codecommit.GetRepositoryInput, ...func(*codecommit.Options)) *codecommit.GetRepositoryOutput); ok {
r0 = returnFunc(context1, getRepositoryInput, fns...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*codecommit.GetRepositoryOutput)
}
}
if returnFunc, ok := ret.Get(1).(func(aws.Context, *codecommit.GetRepositoryInput, ...request.Option) error); ok {
r1 = returnFunc(v, getRepositoryInput, options...)
if returnFunc, ok := ret.Get(1).(func(context.Context, *codecommit.GetRepositoryInput, ...func(*codecommit.Options)) error); ok {
r1 = returnFunc(context1, getRepositoryInput, fns...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AWSCodeCommitClient_GetRepositoryWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRepositoryWithContext'
type AWSCodeCommitClient_GetRepositoryWithContext_Call struct {
// AWSCodeCommitClient_GetRepository_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRepository'
type AWSCodeCommitClient_GetRepository_Call struct {
*mock.Call
}
// GetRepositoryWithContext is a helper method to define mock.On call
// - v aws.Context
// GetRepository is a helper method to define mock.On call
// - context1 context.Context
// - getRepositoryInput *codecommit.GetRepositoryInput
// - options ...request.Option
func (_e *AWSCodeCommitClient_Expecter) GetRepositoryWithContext(v interface{}, getRepositoryInput interface{}, options ...interface{}) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
return &AWSCodeCommitClient_GetRepositoryWithContext_Call{Call: _e.mock.On("GetRepositoryWithContext",
append([]interface{}{v, getRepositoryInput}, options...)...)}
// - fns ...func(*codecommit.Options)
func (_e *AWSCodeCommitClient_Expecter) GetRepository(context1 interface{}, getRepositoryInput interface{}, fns ...interface{}) *AWSCodeCommitClient_GetRepository_Call {
return &AWSCodeCommitClient_GetRepository_Call{Call: _e.mock.On("GetRepository",
append([]interface{}{context1, getRepositoryInput}, fns...)...)}
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Run(run func(v aws.Context, getRepositoryInput *codecommit.GetRepositoryInput, options ...request.Option)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
func (_c *AWSCodeCommitClient_GetRepository_Call) Run(run func(context1 context.Context, getRepositoryInput *codecommit.GetRepositoryInput, fns ...func(*codecommit.Options))) *AWSCodeCommitClient_GetRepository_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 aws.Context
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(aws.Context)
arg0 = args[0].(context.Context)
}
var arg1 *codecommit.GetRepositoryInput
if args[1] != nil {
arg1 = args[1].(*codecommit.GetRepositoryInput)
}
var arg2 []request.Option
variadicArgs := make([]request.Option, len(args)-2)
var arg2 []func(*codecommit.Options)
variadicArgs := make([]func(*codecommit.Options), len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
variadicArgs[i] = a.(func(*codecommit.Options))
}
}
arg2 = variadicArgs
@ -202,81 +202,81 @@ func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Run(run func(v aws.
return _c
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Return(getRepositoryOutput *codecommit.GetRepositoryOutput, err error) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
func (_c *AWSCodeCommitClient_GetRepository_Call) Return(getRepositoryOutput *codecommit.GetRepositoryOutput, err error) *AWSCodeCommitClient_GetRepository_Call {
_c.Call.Return(getRepositoryOutput, err)
return _c
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) RunAndReturn(run func(v aws.Context, getRepositoryInput *codecommit.GetRepositoryInput, options ...request.Option) (*codecommit.GetRepositoryOutput, error)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
func (_c *AWSCodeCommitClient_GetRepository_Call) RunAndReturn(run func(context1 context.Context, getRepositoryInput *codecommit.GetRepositoryInput, fns ...func(*codecommit.Options)) (*codecommit.GetRepositoryOutput, error)) *AWSCodeCommitClient_GetRepository_Call {
_c.Call.Return(run)
return _c
}
// ListBranchesWithContext provides a mock function for the type AWSCodeCommitClient
func (_mock *AWSCodeCommitClient) ListBranchesWithContext(v aws.Context, listBranchesInput *codecommit.ListBranchesInput, options ...request.Option) (*codecommit.ListBranchesOutput, error) {
// request.Option
_va := make([]interface{}, len(options))
for _i := range options {
_va[_i] = options[_i]
// ListBranches provides a mock function for the type AWSCodeCommitClient
func (_mock *AWSCodeCommitClient) ListBranches(context1 context.Context, listBranchesInput *codecommit.ListBranchesInput, fns ...func(*codecommit.Options)) (*codecommit.ListBranchesOutput, error) {
// func(*codecommit.Options)
_va := make([]interface{}, len(fns))
for _i := range fns {
_va[_i] = fns[_i]
}
var _ca []interface{}
_ca = append(_ca, v, listBranchesInput)
_ca = append(_ca, context1, listBranchesInput)
_ca = append(_ca, _va...)
ret := _mock.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for ListBranchesWithContext")
panic("no return value specified for ListBranches")
}
var r0 *codecommit.ListBranchesOutput
var r1 error
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)); ok {
return returnFunc(v, listBranchesInput, options...)
if returnFunc, ok := ret.Get(0).(func(context.Context, *codecommit.ListBranchesInput, ...func(*codecommit.Options)) (*codecommit.ListBranchesOutput, error)); ok {
return returnFunc(context1, listBranchesInput, fns...)
}
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.ListBranchesInput, ...request.Option) *codecommit.ListBranchesOutput); ok {
r0 = returnFunc(v, listBranchesInput, options...)
if returnFunc, ok := ret.Get(0).(func(context.Context, *codecommit.ListBranchesInput, ...func(*codecommit.Options)) *codecommit.ListBranchesOutput); ok {
r0 = returnFunc(context1, listBranchesInput, fns...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*codecommit.ListBranchesOutput)
}
}
if returnFunc, ok := ret.Get(1).(func(aws.Context, *codecommit.ListBranchesInput, ...request.Option) error); ok {
r1 = returnFunc(v, listBranchesInput, options...)
if returnFunc, ok := ret.Get(1).(func(context.Context, *codecommit.ListBranchesInput, ...func(*codecommit.Options)) error); ok {
r1 = returnFunc(context1, listBranchesInput, fns...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AWSCodeCommitClient_ListBranchesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBranchesWithContext'
type AWSCodeCommitClient_ListBranchesWithContext_Call struct {
// AWSCodeCommitClient_ListBranches_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBranches'
type AWSCodeCommitClient_ListBranches_Call struct {
*mock.Call
}
// ListBranchesWithContext is a helper method to define mock.On call
// - v aws.Context
// ListBranches is a helper method to define mock.On call
// - context1 context.Context
// - listBranchesInput *codecommit.ListBranchesInput
// - options ...request.Option
func (_e *AWSCodeCommitClient_Expecter) ListBranchesWithContext(v interface{}, listBranchesInput interface{}, options ...interface{}) *AWSCodeCommitClient_ListBranchesWithContext_Call {
return &AWSCodeCommitClient_ListBranchesWithContext_Call{Call: _e.mock.On("ListBranchesWithContext",
append([]interface{}{v, listBranchesInput}, options...)...)}
// - fns ...func(*codecommit.Options)
func (_e *AWSCodeCommitClient_Expecter) ListBranches(context1 interface{}, listBranchesInput interface{}, fns ...interface{}) *AWSCodeCommitClient_ListBranches_Call {
return &AWSCodeCommitClient_ListBranches_Call{Call: _e.mock.On("ListBranches",
append([]interface{}{context1, listBranchesInput}, fns...)...)}
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Run(run func(v aws.Context, listBranchesInput *codecommit.ListBranchesInput, options ...request.Option)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
func (_c *AWSCodeCommitClient_ListBranches_Call) Run(run func(context1 context.Context, listBranchesInput *codecommit.ListBranchesInput, fns ...func(*codecommit.Options))) *AWSCodeCommitClient_ListBranches_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 aws.Context
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(aws.Context)
arg0 = args[0].(context.Context)
}
var arg1 *codecommit.ListBranchesInput
if args[1] != nil {
arg1 = args[1].(*codecommit.ListBranchesInput)
}
var arg2 []request.Option
variadicArgs := make([]request.Option, len(args)-2)
var arg2 []func(*codecommit.Options)
variadicArgs := make([]func(*codecommit.Options), len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
variadicArgs[i] = a.(func(*codecommit.Options))
}
}
arg2 = variadicArgs
@ -289,81 +289,81 @@ func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Run(run func(v aws.C
return _c
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Return(listBranchesOutput *codecommit.ListBranchesOutput, err error) *AWSCodeCommitClient_ListBranchesWithContext_Call {
func (_c *AWSCodeCommitClient_ListBranches_Call) Return(listBranchesOutput *codecommit.ListBranchesOutput, err error) *AWSCodeCommitClient_ListBranches_Call {
_c.Call.Return(listBranchesOutput, err)
return _c
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) RunAndReturn(run func(v aws.Context, listBranchesInput *codecommit.ListBranchesInput, options ...request.Option) (*codecommit.ListBranchesOutput, error)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
func (_c *AWSCodeCommitClient_ListBranches_Call) RunAndReturn(run func(context1 context.Context, listBranchesInput *codecommit.ListBranchesInput, fns ...func(*codecommit.Options)) (*codecommit.ListBranchesOutput, error)) *AWSCodeCommitClient_ListBranches_Call {
_c.Call.Return(run)
return _c
}
// ListRepositoriesWithContext provides a mock function for the type AWSCodeCommitClient
func (_mock *AWSCodeCommitClient) ListRepositoriesWithContext(v aws.Context, listRepositoriesInput *codecommit.ListRepositoriesInput, options ...request.Option) (*codecommit.ListRepositoriesOutput, error) {
// request.Option
_va := make([]interface{}, len(options))
for _i := range options {
_va[_i] = options[_i]
// ListRepositories provides a mock function for the type AWSCodeCommitClient
func (_mock *AWSCodeCommitClient) ListRepositories(context1 context.Context, listRepositoriesInput *codecommit.ListRepositoriesInput, fns ...func(*codecommit.Options)) (*codecommit.ListRepositoriesOutput, error) {
// func(*codecommit.Options)
_va := make([]interface{}, len(fns))
for _i := range fns {
_va[_i] = fns[_i]
}
var _ca []interface{}
_ca = append(_ca, v, listRepositoriesInput)
_ca = append(_ca, context1, listRepositoriesInput)
_ca = append(_ca, _va...)
ret := _mock.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for ListRepositoriesWithContext")
panic("no return value specified for ListRepositories")
}
var r0 *codecommit.ListRepositoriesOutput
var r1 error
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)); ok {
return returnFunc(v, listRepositoriesInput, options...)
if returnFunc, ok := ret.Get(0).(func(context.Context, *codecommit.ListRepositoriesInput, ...func(*codecommit.Options)) (*codecommit.ListRepositoriesOutput, error)); ok {
return returnFunc(context1, listRepositoriesInput, fns...)
}
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.ListRepositoriesInput, ...request.Option) *codecommit.ListRepositoriesOutput); ok {
r0 = returnFunc(v, listRepositoriesInput, options...)
if returnFunc, ok := ret.Get(0).(func(context.Context, *codecommit.ListRepositoriesInput, ...func(*codecommit.Options)) *codecommit.ListRepositoriesOutput); ok {
r0 = returnFunc(context1, listRepositoriesInput, fns...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*codecommit.ListRepositoriesOutput)
}
}
if returnFunc, ok := ret.Get(1).(func(aws.Context, *codecommit.ListRepositoriesInput, ...request.Option) error); ok {
r1 = returnFunc(v, listRepositoriesInput, options...)
if returnFunc, ok := ret.Get(1).(func(context.Context, *codecommit.ListRepositoriesInput, ...func(*codecommit.Options)) error); ok {
r1 = returnFunc(context1, listRepositoriesInput, fns...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AWSCodeCommitClient_ListRepositoriesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRepositoriesWithContext'
type AWSCodeCommitClient_ListRepositoriesWithContext_Call struct {
// AWSCodeCommitClient_ListRepositories_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRepositories'
type AWSCodeCommitClient_ListRepositories_Call struct {
*mock.Call
}
// ListRepositoriesWithContext is a helper method to define mock.On call
// - v aws.Context
// ListRepositories is a helper method to define mock.On call
// - context1 context.Context
// - listRepositoriesInput *codecommit.ListRepositoriesInput
// - options ...request.Option
func (_e *AWSCodeCommitClient_Expecter) ListRepositoriesWithContext(v interface{}, listRepositoriesInput interface{}, options ...interface{}) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
return &AWSCodeCommitClient_ListRepositoriesWithContext_Call{Call: _e.mock.On("ListRepositoriesWithContext",
append([]interface{}{v, listRepositoriesInput}, options...)...)}
// - fns ...func(*codecommit.Options)
func (_e *AWSCodeCommitClient_Expecter) ListRepositories(context1 interface{}, listRepositoriesInput interface{}, fns ...interface{}) *AWSCodeCommitClient_ListRepositories_Call {
return &AWSCodeCommitClient_ListRepositories_Call{Call: _e.mock.On("ListRepositories",
append([]interface{}{context1, listRepositoriesInput}, fns...)...)}
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Run(run func(v aws.Context, listRepositoriesInput *codecommit.ListRepositoriesInput, options ...request.Option)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
func (_c *AWSCodeCommitClient_ListRepositories_Call) Run(run func(context1 context.Context, listRepositoriesInput *codecommit.ListRepositoriesInput, fns ...func(*codecommit.Options))) *AWSCodeCommitClient_ListRepositories_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 aws.Context
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(aws.Context)
arg0 = args[0].(context.Context)
}
var arg1 *codecommit.ListRepositoriesInput
if args[1] != nil {
arg1 = args[1].(*codecommit.ListRepositoriesInput)
}
var arg2 []request.Option
variadicArgs := make([]request.Option, len(args)-2)
var arg2 []func(*codecommit.Options)
variadicArgs := make([]func(*codecommit.Options), len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
variadicArgs[i] = a.(func(*codecommit.Options))
}
}
arg2 = variadicArgs
@ -376,12 +376,12 @@ func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Run(run func(v a
return _c
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Return(listRepositoriesOutput *codecommit.ListRepositoriesOutput, err error) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
func (_c *AWSCodeCommitClient_ListRepositories_Call) Return(listRepositoriesOutput *codecommit.ListRepositoriesOutput, err error) *AWSCodeCommitClient_ListRepositories_Call {
_c.Call.Return(listRepositoriesOutput, err)
return _c
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) RunAndReturn(run func(v aws.Context, listRepositoriesInput *codecommit.ListRepositoriesInput, options ...request.Option) (*codecommit.ListRepositoriesOutput, error)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
func (_c *AWSCodeCommitClient_ListRepositories_Call) RunAndReturn(run func(context1 context.Context, listRepositoriesInput *codecommit.ListRepositoriesInput, fns ...func(*codecommit.Options)) (*codecommit.ListRepositoriesOutput, error)) *AWSCodeCommitClient_ListRepositories_Call {
_c.Call.Return(run)
return _c
}

View file

@ -5,9 +5,9 @@
package mocks
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"context"
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi"
mock "github.com/stretchr/testify/mock"
)
@ -38,71 +38,71 @@ func (_m *AWSTaggingClient) EXPECT() *AWSTaggingClient_Expecter {
return &AWSTaggingClient_Expecter{mock: &_m.Mock}
}
// GetResourcesWithContext provides a mock function for the type AWSTaggingClient
func (_mock *AWSTaggingClient) GetResourcesWithContext(v aws.Context, getResourcesInput *resourcegroupstaggingapi.GetResourcesInput, options ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error) {
// request.Option
_va := make([]interface{}, len(options))
for _i := range options {
_va[_i] = options[_i]
// GetResources provides a mock function for the type AWSTaggingClient
func (_mock *AWSTaggingClient) GetResources(context1 context.Context, getResourcesInput *resourcegroupstaggingapi.GetResourcesInput, fns ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error) {
// func(*resourcegroupstaggingapi.Options)
_va := make([]interface{}, len(fns))
for _i := range fns {
_va[_i] = fns[_i]
}
var _ca []interface{}
_ca = append(_ca, v, getResourcesInput)
_ca = append(_ca, context1, getResourcesInput)
_ca = append(_ca, _va...)
ret := _mock.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetResourcesWithContext")
panic("no return value specified for GetResources")
}
var r0 *resourcegroupstaggingapi.GetResourcesOutput
var r1 error
if returnFunc, ok := ret.Get(0).(func(aws.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)); ok {
return returnFunc(v, getResourcesInput, options...)
if returnFunc, ok := ret.Get(0).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error)); ok {
return returnFunc(context1, getResourcesInput, fns...)
}
if returnFunc, ok := ret.Get(0).(func(aws.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) *resourcegroupstaggingapi.GetResourcesOutput); ok {
r0 = returnFunc(v, getResourcesInput, options...)
if returnFunc, ok := ret.Get(0).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...func(*resourcegroupstaggingapi.Options)) *resourcegroupstaggingapi.GetResourcesOutput); ok {
r0 = returnFunc(context1, getResourcesInput, fns...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*resourcegroupstaggingapi.GetResourcesOutput)
}
}
if returnFunc, ok := ret.Get(1).(func(aws.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) error); ok {
r1 = returnFunc(v, getResourcesInput, options...)
if returnFunc, ok := ret.Get(1).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...func(*resourcegroupstaggingapi.Options)) error); ok {
r1 = returnFunc(context1, getResourcesInput, fns...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AWSTaggingClient_GetResourcesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetResourcesWithContext'
type AWSTaggingClient_GetResourcesWithContext_Call struct {
// AWSTaggingClient_GetResources_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetResources'
type AWSTaggingClient_GetResources_Call struct {
*mock.Call
}
// GetResourcesWithContext is a helper method to define mock.On call
// - v aws.Context
// GetResources is a helper method to define mock.On call
// - context1 context.Context
// - getResourcesInput *resourcegroupstaggingapi.GetResourcesInput
// - options ...request.Option
func (_e *AWSTaggingClient_Expecter) GetResourcesWithContext(v interface{}, getResourcesInput interface{}, options ...interface{}) *AWSTaggingClient_GetResourcesWithContext_Call {
return &AWSTaggingClient_GetResourcesWithContext_Call{Call: _e.mock.On("GetResourcesWithContext",
append([]interface{}{v, getResourcesInput}, options...)...)}
// - fns ...func(*resourcegroupstaggingapi.Options)
func (_e *AWSTaggingClient_Expecter) GetResources(context1 interface{}, getResourcesInput interface{}, fns ...interface{}) *AWSTaggingClient_GetResources_Call {
return &AWSTaggingClient_GetResources_Call{Call: _e.mock.On("GetResources",
append([]interface{}{context1, getResourcesInput}, fns...)...)}
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Run(run func(v aws.Context, getResourcesInput *resourcegroupstaggingapi.GetResourcesInput, options ...request.Option)) *AWSTaggingClient_GetResourcesWithContext_Call {
func (_c *AWSTaggingClient_GetResources_Call) Run(run func(context1 context.Context, getResourcesInput *resourcegroupstaggingapi.GetResourcesInput, fns ...func(*resourcegroupstaggingapi.Options))) *AWSTaggingClient_GetResources_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 aws.Context
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(aws.Context)
arg0 = args[0].(context.Context)
}
var arg1 *resourcegroupstaggingapi.GetResourcesInput
if args[1] != nil {
arg1 = args[1].(*resourcegroupstaggingapi.GetResourcesInput)
}
var arg2 []request.Option
variadicArgs := make([]request.Option, len(args)-2)
var arg2 []func(*resourcegroupstaggingapi.Options)
variadicArgs := make([]func(*resourcegroupstaggingapi.Options), len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
variadicArgs[i] = a.(func(*resourcegroupstaggingapi.Options))
}
}
arg2 = variadicArgs
@ -115,12 +115,12 @@ func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Run(run func(v aws.Cont
return _c
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Return(getResourcesOutput *resourcegroupstaggingapi.GetResourcesOutput, err error) *AWSTaggingClient_GetResourcesWithContext_Call {
func (_c *AWSTaggingClient_GetResources_Call) Return(getResourcesOutput *resourcegroupstaggingapi.GetResourcesOutput, err error) *AWSTaggingClient_GetResources_Call {
_c.Call.Return(getResourcesOutput, err)
return _c
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) RunAndReturn(run func(v aws.Context, getResourcesInput *resourcegroupstaggingapi.GetResourcesInput, options ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)) *AWSTaggingClient_GetResourcesWithContext_Call {
func (_c *AWSTaggingClient_GetResources_Call) RunAndReturn(run func(context1 context.Context, getResourcesInput *resourcegroupstaggingapi.GetResourcesInput, fns ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error)) *AWSTaggingClient_GetResources_Call {
_c.Call.Return(run)
return _c
}

View file

@ -3,6 +3,7 @@ package commands
import (
"context"
"fmt"
"math"
"net/http"
"os"
"os/signal"
@ -162,7 +163,7 @@ func NewCommand() *cobra.Command {
},
}
clientConfig = cli.AddKubectlFlagsToCmd(&command)
command.Flags().IntVar(&processorsCount, "processors-count", 1, "Processors count.")
command.Flags().IntVar(&processorsCount, "processors-count", env.ParseNumFromEnv("ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT", 1, 1, math.MaxInt32), "Processors count.")
command.Flags().StringVar(&appLabelSelector, "app-label-selector", "", "App label selector.")
command.Flags().StringVar(&logLevel, "loglevel", env.StringFromEnv("ARGOCD_NOTIFICATIONS_CONTROLLER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
command.Flags().StringVar(&logFormat, "logformat", env.StringFromEnv("ARGOCD_NOTIFICATIONS_CONTROLLER_LOGFORMAT", "json"), "Set the logging format. One of: json|text")

View file

@ -0,0 +1,28 @@
package commands
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewCommandProcessorsCountDefault(t *testing.T) {
t.Setenv("ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT", "4")
cmd := NewCommand()
processorsCount, err := cmd.Flags().GetInt("processors-count")
require.NoError(t, err)
assert.Equal(t, 4, processorsCount)
}
func TestNewCommandProcessorsCountInvalidEnvFallsBackToDefault(t *testing.T) {
t.Setenv("ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT", "0")
cmd := NewCommand()
processorsCount, err := cmd.Flags().GetInt("processors-count")
require.NoError(t, err)
assert.Equal(t, 1, processorsCount)
}

View file

@ -1,7 +1,9 @@
package commands
import (
"crypto/sha256"
"crypto/x509"
"encoding/hex"
stderrors "errors"
"fmt"
"os"
@ -62,6 +64,7 @@ func NewCertAddTLSCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
fromFile string
upsert bool
)
command := &cobra.Command{
Use: "add-tls SERVERNAME",
Short: "Add TLS certificate data for connecting to repository server SERVERNAME",
@ -76,8 +79,10 @@ func NewCertAddTLSCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
os.Exit(1)
}
var certificateArray []string
var err error
var (
certificateArray []string
err error
)
if fromFile != "" {
fmt.Printf("Reading TLS certificate data in PEM format from '%s'\n", fromFile)
@ -86,49 +91,42 @@ func NewCertAddTLSCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
fmt.Println("Enter TLS certificate data in PEM format. Press CTRL-D when finished.")
certificateArray, err = certutil.ParseTLSCertificatesFromStream(os.Stdin)
}
errors.CheckError(err)
certificateList := make([]appsv1.RepositoryCertificate, 0)
uniqueCerts, err := deduplicatePEMCertificates(certificateArray)
errors.CheckError(err)
subjectMap := make(map[string]*x509.Certificate)
for _, entry := range certificateArray {
// We want to make sure to only send valid certificate data to the
// server, so we decode the certificate into X509 structure before
// further processing it.
x509cert, err := certutil.DecodePEMCertificateToX509(entry)
errors.CheckError(err)
// TODO: We need a better way to detect duplicates sent in the stream,
// maybe by using fingerprints? For now, no two certs with the same
// subject may be sent.
if subjectMap[x509cert.Subject.String()] != nil {
fmt.Printf("ERROR: Cert with subject '%s' already seen in the input stream.\n", x509cert.Subject.String())
continue
}
subjectMap[x509cert.Subject.String()] = x509cert
if len(uniqueCerts) == 0 {
fmt.Println("No valid certificates have been detected in the stream.")
return
}
serverName := args[0]
if len(certificateArray) > 0 {
certificateList = append(certificateList, appsv1.RepositoryCertificate{
certificateList := []appsv1.RepositoryCertificate{
{
ServerName: serverName,
CertType: "https",
CertData: []byte(strings.Join(certificateArray, "\n")),
})
certificates, err := certIf.CreateCertificate(ctx, &certificatepkg.RepositoryCertificateCreateRequest{
CertData: []byte(strings.Join(uniqueCerts, "\n")),
},
}
_, err = certIf.CreateCertificate(
ctx,
&certificatepkg.RepositoryCertificateCreateRequest{
Certificates: &appsv1.RepositoryCertificateList{
Items: certificateList,
},
Upsert: upsert,
})
errors.CheckError(err)
fmt.Printf("Created entry with %d PEM certificates for repository server %s\n", len(certificates.Items), serverName)
} else {
fmt.Printf("No valid certificates have been detected in the stream.\n")
}
},
)
errors.CheckError(err)
fmt.Printf(
"Created/updated TLS certificate entry for repository server %s with %d unique PEM certificates\n",
serverName,
len(uniqueCerts),
)
},
}
command.Flags().StringVar(&fromFile, "from", "", "Read TLS certificate data from file (default is to read from stdin)")
@ -136,6 +134,45 @@ func NewCertAddTLSCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
return command
}
// certFingerprintSHA256 returns the SHA256 fingerprint of the given X.509 certificate.
// The fingerprint is returned as a lowercase hexadecimal string.
func certFingerprintSHA256(cert *x509.Certificate) string {
sum := sha256.Sum256(cert.Raw)
return hex.EncodeToString(sum[:])
}
// deduplicatePEMCertificates removes duplicate PEM certificates from the input slice.
// Two certificates are considered duplicates if their SHA256 fingerprints match.
// The function returns a slice of unique certificates in the original order.
// If any certificate cannot be decoded into X.509 format, an error is returned.
func deduplicatePEMCertificates(pems []string) ([]string, error) {
fingerprintMap := make(map[string]struct{})
uniqueCerts := make([]string, 0)
for _, entry := range pems {
x509cert, err := certutil.DecodePEMCertificateToX509(entry)
if err != nil {
return nil, err
}
fingerprint := certFingerprintSHA256(x509cert)
if _, exists := fingerprintMap[fingerprint]; exists {
fmt.Printf(
"WARNING: Duplicate certificate detected (SHA256 fingerprint %s, subject '%s'), skipping.\n",
fingerprint,
x509cert.Subject.String(),
)
continue
}
fingerprintMap[fingerprint] = struct{}{}
uniqueCerts = append(uniqueCerts, entry)
}
return uniqueCerts, nil
}
// NewCertAddSSHCommand returns a new instance of an `argocd cert add` command
func NewCertAddSSHCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (

View file

@ -0,0 +1,108 @@
package commands
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func generateTestCert(t *testing.T, cn string) string {
t.Helper()
priv, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{
CommonName: cn,
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
}
der, err := x509.CreateCertificate(
rand.Reader,
template,
template,
&priv.PublicKey,
priv,
)
require.NoError(t, err)
var buf bytes.Buffer
require.NoError(t, pem.Encode(&buf, &pem.Block{
Type: "CERTIFICATE",
Bytes: der,
}))
return buf.String()
}
func TestDeduplicatePEMCertificates_DuplicateCerts(t *testing.T) {
cert := generateTestCert(t, "repo.example.com")
pems := []string{cert, cert}
unique, err := deduplicatePEMCertificates(pems)
require.NoError(t, err)
require.Len(t, unique, 1)
}
func TestDeduplicatePEMCertificates_SameSubjectDifferentCerts(t *testing.T) {
cert1 := generateTestCert(t, "repo.example.com")
cert2 := generateTestCert(t, "repo.example.com")
pems := []string{cert1, cert2}
unique, err := deduplicatePEMCertificates(pems)
require.NoError(t, err)
require.Len(t, unique, 2)
}
func TestDeduplicatePEMCertificates_InvalidCert(t *testing.T) {
pems := []string{"not a cert"}
_, err := deduplicatePEMCertificates(pems)
require.Error(t, err)
}
func TestDeduplicatePEMCertificates_EmptyInput(t *testing.T) {
unique, err := deduplicatePEMCertificates([]string{})
require.NoError(t, err)
require.Empty(t, unique)
}
func TestDeduplicatePEMCertificates_LargeNumberOfCerts(t *testing.T) {
const numCerts = 100
pems := make([]string, numCerts)
for i := range numCerts {
pems[i] = generateTestCert(t, fmt.Sprintf("host%d.example.com", i))
}
unique, err := deduplicatePEMCertificates(pems)
require.NoError(t, err)
require.Len(t, unique, numCerts)
}
func TestDeduplicatePEMCertificates_MixedValidAndInvalid(t *testing.T) {
cert1 := generateTestCert(t, "valid1.example.com")
cert2 := generateTestCert(t, "valid2.example.com")
// invalid entry before the valid ones — deduplicatePEMCertificates stops at first error
pems := []string{"not a cert", cert1, cert2}
_, err := deduplicatePEMCertificates(pems)
require.Error(t, err)
// invalid entry after some valid ones — same behaviour
pems = []string{cert1, "not a cert", cert2}
_, err = deduplicatePEMCertificates(pems)
require.Error(t, err)
}

View file

@ -13,7 +13,7 @@ import (
)
// NewConfigureCommand returns a new instance of an `argocd configure` command
func NewConfigureCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Command {
func NewConfigureCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var promptsEnabled bool
command := &cobra.Command{
@ -26,7 +26,7 @@ argocd configure --prompts-enabled=true
# Disable optional interactive prompts
argocd configure --prompts-enabled=false`,
Run: func(_ *cobra.Command, _ []string) {
localCfg, err := localconfig.ReadLocalConfig(globalClientOpts.ConfigPath)
localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
errors.CheckError(err)
if localCfg == nil {
fmt.Println("No local configuration found")
@ -35,7 +35,7 @@ argocd configure --prompts-enabled=false`,
localCfg.PromptsEnabled = promptsEnabled
err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
err = localconfig.WriteLocalConfig(*localCfg, clientOpts.ConfigPath)
errors.CheckError(err)
fmt.Println("Successfully updated the following configuration settings:")

View file

@ -36,7 +36,7 @@ import (
)
// NewLoginCommand returns a new instance of `argocd login` command
func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Command {
func NewLoginCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
ctxName string
username string
@ -64,15 +64,15 @@ argocd login cd.argoproj.io --core`,
var server string
if len(args) != 1 && !globalClientOpts.PortForward && !globalClientOpts.Core {
if len(args) != 1 && !clientOpts.PortForward && !clientOpts.Core {
c.HelpFunc()(c, args)
os.Exit(1)
}
switch {
case globalClientOpts.PortForward:
case clientOpts.PortForward:
server = "port-forward"
case globalClientOpts.Core:
case clientOpts.Core:
server = "kubernetes"
default:
server = args[0]
@ -82,42 +82,42 @@ argocd login cd.argoproj.io --core`,
tlsTestResult, err := grpc_util.TestTLS(server, dialTime)
errors.CheckError(err)
if !tlsTestResult.TLS {
if !globalClientOpts.PlainText {
if !clientOpts.PlainText {
if !cli.AskToProceed("WARNING: server is not configured with TLS. Proceed (y/n)? ") {
os.Exit(1)
}
globalClientOpts.PlainText = true
clientOpts.PlainText = true
}
} else if tlsTestResult.InsecureErr != nil {
if !globalClientOpts.Insecure {
if !clientOpts.Insecure {
if !cli.AskToProceed(fmt.Sprintf("WARNING: server certificate had error: %s. Proceed insecurely (y/n)? ", tlsTestResult.InsecureErr)) {
os.Exit(1)
}
globalClientOpts.Insecure = true
clientOpts.Insecure = true
}
}
}
}
clientOpts := argocdclient.ClientOptions{
loginOpts := argocdclient.ClientOptions{
ConfigPath: "",
ServerAddr: server,
Insecure: globalClientOpts.Insecure,
PlainText: globalClientOpts.PlainText,
ClientCertFile: globalClientOpts.ClientCertFile,
ClientCertKeyFile: globalClientOpts.ClientCertKeyFile,
GRPCWeb: globalClientOpts.GRPCWeb,
GRPCWebRootPath: globalClientOpts.GRPCWebRootPath,
PortForward: globalClientOpts.PortForward,
PortForwardNamespace: globalClientOpts.PortForwardNamespace,
Headers: globalClientOpts.Headers,
KubeOverrides: globalClientOpts.KubeOverrides,
ServerName: globalClientOpts.ServerName,
Insecure: clientOpts.Insecure,
PlainText: clientOpts.PlainText,
ClientCertFile: clientOpts.ClientCertFile,
ClientCertKeyFile: clientOpts.ClientCertKeyFile,
GRPCWeb: clientOpts.GRPCWeb,
GRPCWebRootPath: clientOpts.GRPCWebRootPath,
PortForward: clientOpts.PortForward,
PortForwardNamespace: clientOpts.PortForwardNamespace,
Headers: clientOpts.Headers,
KubeOverrides: clientOpts.KubeOverrides,
ServerName: clientOpts.ServerName,
}
if ctxName == "" {
ctxName = server
if globalClientOpts.GRPCWebRootPath != "" {
rootPath := strings.TrimRight(strings.TrimLeft(globalClientOpts.GRPCWebRootPath, "/"), "/")
if loginOpts.GRPCWebRootPath != "" {
rootPath := strings.TrimRight(strings.TrimLeft(loginOpts.GRPCWebRootPath, "/"), "/")
ctxName = fmt.Sprintf("%s/%s", server, rootPath)
}
}
@ -125,8 +125,8 @@ argocd login cd.argoproj.io --core`,
// Perform the login
var tokenString string
var refreshToken string
if !globalClientOpts.Core {
acdClient := headless.NewClientOrDie(&clientOpts, c)
if !clientOpts.Core {
acdClient := headless.NewClientOrDie(&loginOpts, c)
setConn, setIf := acdClient.NewSettingsClientOrDie()
defer utilio.Close(setConn)
if !sso {
@ -149,18 +149,18 @@ argocd login cd.argoproj.io --core`,
}
// login successful. Persist the config
localCfg, err := localconfig.ReadLocalConfig(globalClientOpts.ConfigPath)
localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
errors.CheckError(err)
if localCfg == nil {
localCfg = &localconfig.LocalConfig{}
}
localCfg.UpsertServer(localconfig.Server{
Server: server,
PlainText: globalClientOpts.PlainText,
Insecure: globalClientOpts.Insecure,
GRPCWeb: globalClientOpts.GRPCWeb,
GRPCWebRootPath: globalClientOpts.GRPCWebRootPath,
Core: globalClientOpts.Core,
PlainText: clientOpts.PlainText,
Insecure: clientOpts.Insecure,
GRPCWeb: clientOpts.GRPCWeb,
GRPCWebRootPath: clientOpts.GRPCWebRootPath,
Core: clientOpts.Core,
})
localCfg.UpsertUser(localconfig.User{
Name: ctxName,
@ -176,7 +176,7 @@ argocd login cd.argoproj.io --core`,
User: ctxName,
Server: server,
})
err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
err = localconfig.WriteLocalConfig(*localCfg, clientOpts.ConfigPath)
errors.CheckError(err)
fmt.Printf("Context '%s' updated\n", ctxName)
},

View file

@ -22,7 +22,7 @@ import (
)
// NewLogoutCommand returns a new instance of `argocd logout` command
func NewLogoutCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Command {
func NewLogoutCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
command := &cobra.Command{
Use: "logout CONTEXT",
Short: "Log out from Argo CD",
@ -41,42 +41,42 @@ argocd logout cd.argoproj.io
}
context := args[0]
localCfg, err := localconfig.ReadLocalConfig(globalClientOpts.ConfigPath)
localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
errutil.CheckError(err)
if localCfg == nil {
log.Fatalf("Nothing to logout from")
}
promptUtil := utils.NewPrompt(globalClientOpts.PromptsEnabled)
promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
canLogout := promptUtil.Confirm(fmt.Sprintf("Are you sure you want to log out from '%s'?", context))
if canLogout {
if tlsTestResult, err := grpc_util.TestTLS(context, common.BearerTokenTimeout); err != nil {
log.Warnf("failed to check the TLS config settings for the server : %v.", err)
globalClientOpts.PlainText = true
clientOpts.PlainText = true
} else {
if !tlsTestResult.TLS {
if !globalClientOpts.PlainText {
if !clientOpts.PlainText {
if !cli.AskToProceed("WARNING: server is not configured with TLS. Proceed (y/n)? ") {
os.Exit(1)
}
globalClientOpts.PlainText = true
clientOpts.PlainText = true
}
} else if tlsTestResult.InsecureErr != nil {
if !globalClientOpts.Insecure {
if !clientOpts.Insecure {
if !cli.AskToProceed(fmt.Sprintf("WARNING: server certificate had error: %s. Proceed insecurely (y/n)? ", tlsTestResult.InsecureErr)) {
os.Exit(1)
}
globalClientOpts.Insecure = true
clientOpts.Insecure = true
}
}
}
scheme := "https"
if globalClientOpts.PlainText {
if clientOpts.PlainText {
scheme = "http"
}
if res, err := revokeServerToken(scheme, context, localCfg.GetToken(context), globalClientOpts.Insecure); err != nil {
if res, err := revokeServerToken(scheme, context, localCfg.GetToken(context), clientOpts.Insecure); err != nil {
log.Warnf("failed to invalidate token on server: %v.", err)
} else {
_ = res.Body.Close()
@ -97,7 +97,7 @@ argocd logout cd.argoproj.io
if err != nil {
log.Fatalf("Error in logging out: %s", err)
}
err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
err = localconfig.WriteLocalConfig(*localCfg, clientOpts.ConfigPath)
errutil.CheckError(err)
fmt.Printf("Logged out from '%s'\n", context)

View file

@ -19,7 +19,7 @@ import (
)
// NewReloginCommand returns a new instance of `argocd relogin` command
func NewReloginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Command {
func NewReloginCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
password string
callback string
@ -37,7 +37,7 @@ func NewReloginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comm
c.HelpFunc()(c, args)
os.Exit(1)
}
localCfg, err := localconfig.ReadLocalConfig(globalClientOpts.ConfigPath)
localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
errors.CheckError(err)
if localCfg == nil {
log.Fatalf("No context found. Login using `argocd login`")
@ -47,18 +47,18 @@ func NewReloginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comm
var tokenString string
var refreshToken string
clientOpts := argocdclient.ClientOptions{
reloginOpts := argocdclient.ClientOptions{
ConfigPath: "",
ServerAddr: configCtx.Server.Server,
Insecure: configCtx.Server.Insecure,
ClientCertFile: globalClientOpts.ClientCertFile,
ClientCertKeyFile: globalClientOpts.ClientCertKeyFile,
GRPCWeb: globalClientOpts.GRPCWeb,
GRPCWebRootPath: globalClientOpts.GRPCWebRootPath,
ClientCertFile: clientOpts.ClientCertFile,
ClientCertKeyFile: clientOpts.ClientCertKeyFile,
GRPCWeb: clientOpts.GRPCWeb,
GRPCWebRootPath: clientOpts.GRPCWebRootPath,
PlainText: configCtx.Server.PlainText,
Headers: globalClientOpts.Headers,
Headers: clientOpts.Headers,
}
acdClient := headless.NewClientOrDie(&clientOpts, c)
acdClient := headless.NewClientOrDie(&reloginOpts, c)
claims, err := configCtx.User.Claims()
errors.CheckError(err)
if jwtutil.StringField(claims, "iss") == session.SessionManagerClaimsIssuer {
@ -83,7 +83,7 @@ func NewReloginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comm
AuthToken: tokenString,
RefreshToken: refreshToken,
})
err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
err = localconfig.WriteLocalConfig(*localCfg, clientOpts.ConfigPath)
errors.CheckError(err)
fmt.Printf("Context '%s' updated\n", localCfg.CurrentContext)
},

View file

@ -11,11 +11,11 @@ import (
)
func TestNewReloginCommand(t *testing.T) {
globalClientOpts := argocdclient.ClientOptions{
clientOpts := argocdclient.ClientOptions{
ConfigPath: "/path/to/config",
}
cmd := NewReloginCommand(&globalClientOpts)
cmd := NewReloginCommand(&clientOpts)
assert.Equal(t, "relogin", cmd.Use, "Unexpected command Use")
assert.Equal(t, "Refresh an expired authenticate token", cmd.Short, "Unexpected command Short")
@ -33,8 +33,8 @@ func TestNewReloginCommand(t *testing.T) {
assert.Equal(t, 8085, port, "Unexpected default value for --sso-port flag")
}
func TestNewReloginCommandWithGlobalClientOptions(t *testing.T) {
globalClientOpts := argocdclient.ClientOptions{
func TestNewReloginCommandWithClientOptions(t *testing.T) {
clientOpts := argocdclient.ClientOptions{
ConfigPath: "/path/to/config",
ServerAddr: "https://argocd-server.example.com",
Insecure: true,
@ -46,7 +46,7 @@ func TestNewReloginCommandWithGlobalClientOptions(t *testing.T) {
Headers: []string{"header1", "header2"},
}
cmd := NewReloginCommand(&globalClientOpts)
cmd := NewReloginCommand(&clientOpts)
assert.Equal(t, "relogin", cmd.Use, "Unexpected command Use")
assert.Equal(t, "Refresh an expired authenticate token", cmd.Short, "Unexpected command Short")

View file

@ -393,6 +393,8 @@ data:
notificationscontroller.log.level: "info"
# Set the logging format. One of: json|text (default "json")
notificationscontroller.log.format: "json"
# Number of notification processors (default 1)
notificationscontroller.processors.count: "1"
# Enable self-service notifications config. Used in conjunction with apps-in-any-namespace. (default "false")
notificationscontroller.selfservice.enabled: "false"
# Disable TLS on connections to repo server

View file

@ -70,6 +70,19 @@ data:
notificationscontroller.selfservice.enabled: "true"
```
Other notifications controller startup settings can be managed the same way. For notifications-heavy
installations, `notificationscontroller.processors.count` configures the worker concurrency and maps
to the `--processors-count` startup flag. For example:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cmd-params-cm
data:
notificationscontroller.processors.count: "4"
```
To use this feature, you can deploy configmap named `argocd-notifications-cm` and possibly a secret `argocd-notifications-secret` in the namespace where the Argo CD application lives.
When it is configured this way the controller will send notifications using both the controller level configuration (the configmap located in the same namespaces as the controller) as well as

View file

@ -531,6 +531,22 @@ func (sc *syncContext) Sync() {
multiStep := tasks.multiStep()
runningTasks := tasks.Filter(func(t *syncTask) bool { return (multiStep || t.isHook()) && t.running() })
if runningTasks.Len() > 0 {
// check if any of the running task's resources are missing to prevent infinite loop of waiting for healthy
for _, task := range runningTasks {
if task.liveObj == nil {
liveObj, err := sc.getResource(task)
if err != nil && !apierrors.IsNotFound(err) {
sc.setResourceResult(task, task.syncStatus, common.OperationError, fmt.Sprintf("Failed to get live resource %v", err))
continue
}
if liveObj != nil {
continue
}
sc.setResourceResult(task, common.ResultCodeSyncFailed, common.OperationError, fmt.Sprintf("Resource %s/%s/%s is missing, it might have been deleted", task.group(), task.kind(), task.name()))
}
}
sc.setRunningPhase(runningTasks, false)
return
}

View file

@ -111,6 +111,17 @@ func TestSyncNamespaceCreatedBeforeDryRunWithoutFailure(t *testing.T) {
Live: []*unstructured.Unstructured{nil, nil},
Target: []*unstructured.Unstructured{pod},
})
ns := &unstructured.Unstructured{}
ns.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"})
ns.SetName(testingutils.FakeArgoCDNamespace)
fakeDynamicClient := fake.NewSimpleDynamicClient(runtime.NewScheme())
fakeDynamicClient.PrependReactor("get", "namespaces", func(action testcore.Action) (bool, runtime.Object, error) {
return true, ns, nil
})
syncCtx.dynamicIf = fakeDynamicClient
syncCtx.Sync()
phase, msg, resources := syncCtx.GetState()
assert.Equal(t, synccommon.OperationRunning, phase)
@ -348,6 +359,102 @@ func TestSyncSuccessfully_Multistep(t *testing.T) {
assert.Len(t, resources, 2)
}
func TestSync_MultistepResourceDeletionMidstep(t *testing.T) {
pod1 := testingutils.NewPod()
pod1.SetName("pod-1")
pod1.SetNamespace("fake-argocd-ns")
pod1.SetAnnotations(map[string]string{synccommon.AnnotationSyncWave: "1"})
pod2 := testingutils.NewPod()
pod2.SetName("pod-2")
pod2.SetNamespace("fake-argocd-ns")
pod2.SetAnnotations(map[string]string{synccommon.AnnotationSyncWave: "2"})
tests := []struct {
name string
resourcesStart map[kube.ResourceKey]reconciledResource
resourcesChange map[kube.ResourceKey]reconciledResource
statusExpected synccommon.ResultCode
hookPhaseExpected synccommon.OperationPhase
clientGet bool
}{
{
name: "resource deleted during multistep",
resourcesStart: groupResources(ReconciliationResult{
Live: []*unstructured.Unstructured{pod1, pod2},
Target: []*unstructured.Unstructured{pod1, pod2},
}),
resourcesChange: groupResources(ReconciliationResult{
Live: []*unstructured.Unstructured{nil, pod2},
Target: []*unstructured.Unstructured{pod1, pod2},
}),
statusExpected: synccommon.ResultCodeSyncFailed,
hookPhaseExpected: synccommon.OperationError,
clientGet: false,
},
{
name: "no false positive on resource creation",
resourcesStart: groupResources(ReconciliationResult{
Live: []*unstructured.Unstructured{nil, pod2},
Target: []*unstructured.Unstructured{pod1, pod2},
}),
resourcesChange: groupResources(ReconciliationResult{
Live: []*unstructured.Unstructured{pod1, pod2},
Target: []*unstructured.Unstructured{pod1, pod2},
}),
statusExpected: synccommon.ResultCodeSynced,
hookPhaseExpected: synccommon.OperationRunning,
clientGet: false,
},
{
name: "resource created after task sync started",
resourcesStart: groupResources(ReconciliationResult{
Live: []*unstructured.Unstructured{nil, pod2},
Target: []*unstructured.Unstructured{pod1, pod2},
}),
resourcesChange: groupResources(ReconciliationResult{
Live: []*unstructured.Unstructured{nil, pod2},
Target: []*unstructured.Unstructured{pod1, pod2},
}),
statusExpected: synccommon.ResultCodeSynced,
hookPhaseExpected: synccommon.OperationRunning,
clientGet: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
syncCtx := newTestSyncCtx(nil, WithResourceModificationChecker(true, diffResultList()))
syncCtx.resources = tt.resourcesStart
fakeDynamicClient := fake.NewSimpleDynamicClient(runtime.NewScheme())
if tt.clientGet {
fakeDynamicClient.PrependReactor("get", "pods", func(action testcore.Action) (bool, runtime.Object, error) {
return true, pod1, nil
})
}
syncCtx.dynamicIf = fakeDynamicClient
syncCtx.Sync()
phase, _, resources := syncCtx.GetState()
assert.Len(t, resources, 1)
assert.Equal(t, "pod-1", resources[0].ResourceKey.Name)
assert.Equal(t, synccommon.OperationRunning, phase)
assert.Equal(t, synccommon.ResultCodeSynced, resources[0].Status)
assert.Equal(t, synccommon.OperationRunning, resources[0].HookPhase)
syncCtx.resources = tt.resourcesChange
syncCtx.Sync()
phase, _, resources = syncCtx.GetState()
assert.Equal(t, synccommon.OperationRunning, phase)
assert.Len(t, resources, 1)
assert.Equal(t, "pod-1", resources[0].ResourceKey.Name)
assert.Equal(t, tt.statusExpected, resources[0].Status)
assert.Equal(t, tt.hookPhaseExpected, resources[0].HookPhase)
})
}
}
func TestSyncDeleteSuccessfully(t *testing.T) {
syncCtx := newTestSyncCtx(nil, WithOperationSettings(false, true, false, false))
svc := testingutils.NewService()
@ -514,6 +621,12 @@ func TestSync_ApplyOutOfSyncOnly(t *testing.T) {
Target: []*unstructured.Unstructured{pod1, nil, pod3},
})
fakeDynamicClient := fake.NewSimpleDynamicClient(runtime.NewScheme())
fakeDynamicClient.PrependReactor("get", "pods", func(action testcore.Action) (bool, runtime.Object, error) {
return true, pod1, nil
})
syncCtx.dynamicIf = fakeDynamicClient
syncCtx.Sync()
phase, _, resources := syncCtx.GetState()
assert.Equal(t, synccommon.OperationRunning, phase)
@ -1930,6 +2043,9 @@ func TestSync_SyncWaveHook(t *testing.T) {
})
syncCtx.hooks = []*unstructured.Unstructured{pod3}
fakeDynamicClient := fake.NewSimpleDynamicClient(runtime.NewScheme())
syncCtx.dynamicIf = fakeDynamicClient
called := false
syncCtx.syncWaveHook = func(phase synccommon.SyncPhase, wave int, final bool) error {
called = true

View file

@ -17,6 +17,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery"
@ -65,7 +66,7 @@ type kubectlServerSideDiffDryRunApplier struct {
openAPISchema openapi.Resources
}
type commandExecutor func(ioStreams genericclioptions.IOStreams, fileName string) error
type commandExecutor func(ioStreams genericiooptions.IOStreams, fileName string) error
func maybeLogManifest(manifestBytes []byte, log logr.Logger) error {
// log manifest
@ -111,7 +112,7 @@ func createManifestFile(obj *unstructured.Unstructured, log logr.Logger) (*os.Fi
return manifestFile, nil
}
func (k *kubectlResourceOperations) runResourceCommand(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, executor commandExecutor) (string, error) {
func (k *kubectlResourceOperations) runResourceCommand(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, reconcileRBAC bool, executor commandExecutor) (string, error) {
manifestFile, err := createManifestFile(obj, k.log)
if err != nil {
return "", err
@ -120,7 +121,7 @@ func (k *kubectlResourceOperations) runResourceCommand(ctx context.Context, obj
var out []string
// rbac resources are first applied with auth reconcile kubectl feature.
if obj.GetAPIVersion() == "rbac.authorization.k8s.io/v1" {
if reconcileRBAC && obj.GetAPIVersion() == "rbac.authorization.k8s.io/v1" {
outReconcile, err := k.rbacReconcile(ctx, obj, manifestFile.Name(), dryRunStrategy)
if err != nil {
return "", fmt.Errorf("error running rbacReconcile: %w", err)
@ -131,7 +132,7 @@ func (k *kubectlResourceOperations) runResourceCommand(ctx context.Context, obj
}
// Run kubectl apply
ioStreams := genericclioptions.IOStreams{
ioStreams := genericiooptions.IOStreams{
In: &bytes.Buffer{},
Out: &bytes.Buffer{},
ErrOut: &bytes.Buffer{},
@ -160,7 +161,7 @@ func (k *kubectlServerSideDiffDryRunApplier) runResourceCommand(obj *unstructure
stderrBuf := &bytes.Buffer{}
// Run kubectl apply
ioStreams := genericclioptions.IOStreams{
ioStreams := genericiooptions.IOStreams{
In: &bytes.Buffer{},
Out: stdoutBuf,
ErrOut: stderrBuf,
@ -226,7 +227,7 @@ func (k *kubectlResourceOperations) ReplaceResource(ctx context.Context, obj *un
span.SetBaggageItem("name", obj.GetName())
defer span.Finish()
k.log.Info(fmt.Sprintf("Replacing resource %s/%s in cluster: %s, namespace: %s", obj.GetKind(), obj.GetName(), k.config.Host, obj.GetNamespace()))
return k.runResourceCommand(ctx, obj, dryRunStrategy, func(ioStreams genericclioptions.IOStreams, fileName string) error {
return k.runResourceCommand(ctx, obj, dryRunStrategy, false, func(ioStreams genericiooptions.IOStreams, fileName string) error {
cleanup, err := processKubectlRun(k.onKubectlRun, "replace")
if err != nil {
return err
@ -248,7 +249,7 @@ func (k *kubectlResourceOperations) CreateResource(ctx context.Context, obj *uns
span.SetBaggageItem("kind", gvk.Kind)
span.SetBaggageItem("name", obj.GetName())
defer span.Finish()
return k.runResourceCommand(ctx, obj, dryRunStrategy, func(ioStreams genericclioptions.IOStreams, fileName string) error {
return k.runResourceCommand(ctx, obj, dryRunStrategy, false, func(ioStreams genericiooptions.IOStreams, fileName string) error {
cleanup, err := processKubectlRun(k.onKubectlRun, "create")
if err != nil {
return err
@ -313,7 +314,7 @@ func (k *kubectlServerSideDiffDryRunApplier) ApplyResource(_ context.Context, ob
"manager", manager,
"serverSideApply", serverSideApply).Info(fmt.Sprintf("Running server-side diff. Dry run applying resource %s/%s in cluster: %s, namespace: %s", obj.GetKind(), obj.GetName(), k.config.Host, obj.GetNamespace()))
return k.runResourceCommand(obj, func(ioStreams genericclioptions.IOStreams, fileName string) error {
return k.runResourceCommand(obj, func(ioStreams genericiooptions.IOStreams, fileName string) error {
cleanup, err := processKubectlRun(k.onKubectlRun, "apply")
if err != nil {
return err
@ -344,7 +345,7 @@ func (k *kubectlResourceOperations) ApplyResource(ctx context.Context, obj *unst
"serverSideApply", serverSideApply,
"serverSideDiff", true).Info(fmt.Sprintf("Applying resource %s/%s in cluster: %s, namespace: %s", obj.GetKind(), obj.GetName(), k.config.Host, obj.GetNamespace()))
return k.runResourceCommand(ctx, obj, dryRunStrategy, func(ioStreams genericclioptions.IOStreams, fileName string) error {
return k.runResourceCommand(ctx, obj, dryRunStrategy, true, func(ioStreams genericiooptions.IOStreams, fileName string) error {
cleanup, err := processKubectlRun(k.onKubectlRun, "apply")
if err != nil {
return err
@ -359,7 +360,7 @@ func (k *kubectlResourceOperations) ApplyResource(ctx context.Context, obj *unst
})
}
func newApplyOptionsCommon(config *rest.Config, fact cmdutil.Factory, ioStreams genericclioptions.IOStreams, obj *unstructured.Unstructured, fileName string, validate bool, force, serverSideApply bool, dryRunStrategy cmdutil.DryRunStrategy, manager string) (*apply.ApplyOptions, error) {
func newApplyOptionsCommon(config *rest.Config, fact cmdutil.Factory, ioStreams genericiooptions.IOStreams, obj *unstructured.Unstructured, fileName string, validate bool, force, serverSideApply bool, dryRunStrategy cmdutil.DryRunStrategy, manager string) (*apply.ApplyOptions, error) {
flags := apply.NewApplyFlags(ioStreams)
o := &apply.ApplyOptions{
IOStreams: ioStreams,
@ -407,7 +408,7 @@ func newApplyOptionsCommon(config *rest.Config, fact cmdutil.Factory, ioStreams
return o, nil
}
func (k *kubectlServerSideDiffDryRunApplier) newApplyOptions(ioStreams genericclioptions.IOStreams, obj *unstructured.Unstructured, fileName string, validate bool, force, serverSideApply bool, dryRunStrategy cmdutil.DryRunStrategy, manager string) (*apply.ApplyOptions, error) {
func (k *kubectlServerSideDiffDryRunApplier) newApplyOptions(ioStreams genericiooptions.IOStreams, obj *unstructured.Unstructured, fileName string, validate bool, force, serverSideApply bool, dryRunStrategy cmdutil.DryRunStrategy, manager string) (*apply.ApplyOptions, error) {
o, err := newApplyOptionsCommon(k.config, k.fact, ioStreams, obj, fileName, validate, force, serverSideApply, dryRunStrategy, manager)
if err != nil {
return nil, err
@ -436,7 +437,7 @@ func (k *kubectlServerSideDiffDryRunApplier) newApplyOptions(ioStreams genericcl
return o, nil
}
func (k *kubectlResourceOperations) newApplyOptions(ioStreams genericclioptions.IOStreams, obj *unstructured.Unstructured, fileName string, validate bool, force, serverSideApply bool, dryRunStrategy cmdutil.DryRunStrategy, manager string) (*apply.ApplyOptions, error) {
func (k *kubectlResourceOperations) newApplyOptions(ioStreams genericiooptions.IOStreams, obj *unstructured.Unstructured, fileName string, validate bool, force, serverSideApply bool, dryRunStrategy cmdutil.DryRunStrategy, manager string) (*apply.ApplyOptions, error) {
o, err := newApplyOptionsCommon(k.config, k.fact, ioStreams, obj, fileName, validate, force, serverSideApply, dryRunStrategy, manager)
if err != nil {
return nil, err
@ -469,7 +470,7 @@ func (k *kubectlResourceOperations) newApplyOptions(ioStreams genericclioptions.
return o, nil
}
func (k *kubectlResourceOperations) newCreateOptions(ioStreams genericclioptions.IOStreams, fileName string, dryRunStrategy cmdutil.DryRunStrategy) (*create.CreateOptions, error) {
func (k *kubectlResourceOperations) newCreateOptions(ioStreams genericiooptions.IOStreams, fileName string, dryRunStrategy cmdutil.DryRunStrategy) (*create.CreateOptions, error) {
o := create.NewCreateOptions(ioStreams)
recorder, err := o.RecordFlags.ToRecorder()
@ -507,7 +508,7 @@ func (k *kubectlResourceOperations) newCreateOptions(ioStreams genericclioptions
return o, nil
}
func (k *kubectlResourceOperations) newReplaceOptions(config *rest.Config, f cmdutil.Factory, ioStreams genericclioptions.IOStreams, fileName string, namespace string, force bool, dryRunStrategy cmdutil.DryRunStrategy) (*replace.ReplaceOptions, error) {
func (k *kubectlResourceOperations) newReplaceOptions(config *rest.Config, f cmdutil.Factory, ioStreams genericiooptions.IOStreams, fileName string, namespace string, force bool, dryRunStrategy cmdutil.DryRunStrategy) (*replace.ReplaceOptions, error) {
o := replace.NewReplaceOptions(ioStreams)
recorder, err := o.RecordFlags.ToRecorder()
@ -565,7 +566,7 @@ func (k *kubectlResourceOperations) newReplaceOptions(config *rest.Config, f cmd
return o, nil
}
func newReconcileOptions(f cmdutil.Factory, kubeClient *kubernetes.Clientset, fileName string, ioStreams genericclioptions.IOStreams, namespace string, dryRunStrategy cmdutil.DryRunStrategy) (*auth.ReconcileOptions, error) {
func newReconcileOptions(f cmdutil.Factory, kubeClient *kubernetes.Clientset, fileName string, ioStreams genericiooptions.IOStreams, namespace string, dryRunStrategy cmdutil.DryRunStrategy) (*auth.ReconcileOptions, error) {
o := auth.NewReconcileOptions(ioStreams)
o.RBACClient = kubeClient.RbacV1()
o.NamespaceClient = kubeClient.CoreV1()
@ -626,7 +627,7 @@ func (k *kubectlResourceOperations) authReconcile(ctx context.Context, obj *unst
return "", fmt.Errorf("error getting namespace %s: %w", obj.GetNamespace(), err)
}
}
ioStreams := genericclioptions.IOStreams{
ioStreams := genericiooptions.IOStreams{
In: &bytes.Buffer{},
Out: &bytes.Buffer{},
ErrOut: &bytes.Buffer{},

View file

@ -1,6 +1,7 @@
package kube
import (
"bytes"
"context"
"encoding/json"
"fmt"
@ -9,10 +10,14 @@ import (
"testing"
testingutils "github.com/argoproj/argo-cd/gitops-engine/pkg/utils/testing"
"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/tracing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions"
"k8s.io/client-go/rest"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
@ -73,3 +78,94 @@ func TestAuthReconcileWithMissingNamespace(t *testing.T) {
_, err = k.authReconcile(context.Background(), clusterRoleBinding, "/dev/null", cmdutil.DryRunNone)
assert.NoError(t, err)
}
func TestRBACReconcileUsage(t *testing.T) {
var executedCommands []string
onKubectlRun := func(cmd string) (CleanupFunc, error) {
executedCommands = append(executedCommands, cmd)
return func() {}, nil
}
k := &kubectlResourceOperations{
onKubectlRun: onKubectlRun,
tracer: &tracing.NopTracer{},
}
role := testingutils.NewRole()
t.Run("CreateResource should NOT call rbacReconcile", func(t *testing.T) {
executedCommands = nil
_, err := k.runResourceCommand(context.Background(), role, cmdutil.DryRunClient, false, func(ioStreams genericiooptions.IOStreams, fileName string) error {
return nil
})
assert.NoError(t, err)
for _, cmd := range executedCommands {
assert.NotEqual(t, "auth", cmd, "auth reconcile should NOT be called")
}
})
t.Run("ReplaceResource should NOT call rbacReconcile", func(t *testing.T) {
executedCommands = nil
_, err := k.runResourceCommand(context.Background(), role, cmdutil.DryRunClient, false, func(ioStreams genericiooptions.IOStreams, fileName string) error {
return nil
})
assert.NoError(t, err)
for _, cmd := range executedCommands {
assert.NotEqual(t, "auth", cmd, "auth reconcile should NOT be called")
}
})
t.Run("Simulation of original issue: when reconcileRBAC is TRUE, it should fail if resource is created by reconcile first", func(t *testing.T) {
// This test simulates the BUGGY behavior (passing reconcileRBAC=true to runResourceCommand)
// and shows why it fails with "already exists".
var executedCommands []string
authCalled := false
kWithAuth := &kubectlResourceOperations{
onKubectlRun: func(cmd string) (CleanupFunc, error) {
executedCommands = append(executedCommands, cmd)
if cmd == "auth" {
authCalled = true
}
return func() {}, nil
},
tracer: &tracing.NopTracer{},
}
// Mock runResourceCommand behavior manually to avoid calling real authReconcile which panics due to nil fact/config
runResourceCommandMock := func(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, reconcileRBAC bool, executor commandExecutor) (string, error) {
if reconcileRBAC && obj.GetAPIVersion() == "rbac.authorization.k8s.io/v1" {
_, err := kWithAuth.onKubectlRun("auth")
require.NoError(t, err)
}
ioStreams := genericiooptions.IOStreams{
In: &bytes.Buffer{},
Out: &bytes.Buffer{},
ErrOut: &bytes.Buffer{},
}
return "", executor(ioStreams, "")
}
executor := func(ioStreams genericiooptions.IOStreams, fileName string) error {
if authCalled {
// Simulate the "already exists" error that happens when kubectl create/replace
// is called after kubectl auth reconcile has already created the resource.
return fmt.Errorf("roles.rbac.authorization.k8s.io \"mytestrole\" already exists")
}
return nil
}
// If we call it with reconcileRBAC = true (the bug), it should fail
_, err := runResourceCommandMock(context.Background(), role, cmdutil.DryRunClient, true, executor)
assert.Error(t, err)
assert.Contains(t, err.Error(), "already exists")
// If we call it with reconcileRBAC = false (the fix), it should succeed
authCalled = false
executedCommands = nil
_, err = runResourceCommandMock(context.Background(), role, cmdutil.DryRunClient, false, executor)
assert.NoError(t, err)
})
}

27
go.mod
View file

@ -7,7 +7,7 @@ require (
dario.cat/mergo v1.0.2
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
github.com/Azure/kubelogin v0.2.15
github.com/Azure/kubelogin v0.2.16
github.com/Masterminds/semver/v3 v3.4.0
github.com/Masterminds/sprig/v3 v3.3.0
github.com/TomOnTime/utfutil v1.0.0
@ -16,10 +16,9 @@ require (
github.com/argoproj/notifications-engine v0.5.1-0.20260213231747-1dbe3de712f8
github.com/argoproj/pkg v0.13.6
github.com/argoproj/pkg/v2 v2.0.1
github.com/aws/aws-sdk-go v1.55.7
github.com/bmatcuk/doublestar/v4 v4.10.0
github.com/bombsimon/logrusr/v4 v4.1.0
github.com/bradleyfalzon/ghinstallation/v2 v2.17.0
github.com/bradleyfalzon/ghinstallation/v2 v2.18.0
github.com/casbin/casbin/v2 v2.135.0
github.com/casbin/govaluate v1.10.0
github.com/cenkalti/backoff/v5 v5.0.3
@ -96,11 +95,11 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0
go.opentelemetry.io/otel/sdk v1.42.0
go.opentelemetry.io/otel/trace v1.42.0
golang.org/x/crypto v0.48.0
golang.org/x/net v0.51.0
golang.org/x/crypto v0.49.0
golang.org/x/net v0.52.0
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.20.0
golang.org/x/term v0.40.0
golang.org/x/term v0.41.0
golang.org/x/time v0.15.0
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57
google.golang.org/grpc v1.79.2
@ -211,7 +210,6 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang/glog v1.2.5 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-github/v75 v75.0.0 // indirect
github.com/google/go-querystring v1.2.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
@ -226,7 +224,6 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.7 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@ -282,12 +279,13 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.41.0 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/tools v0.42.0 // indirect
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
gomodules.xyz/envconfig v1.3.1-0.20190308184047-426f31af0d45 // indirect
@ -317,10 +315,13 @@ require (
)
require (
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.10
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.8
github.com/oklog/ulid/v2 v2.1.1 // indirect
go.uber.org/atomic v1.11.0 // indirect
)
require github.com/google/go-github/v84 v84.0.0 // indirect
replace (
github.com/golang/protobuf => github.com/golang/protobuf v1.5.4
github.com/grpc-ecosystem/grpc-gateway => github.com/grpc-ecosystem/grpc-gateway v1.16.0

44
go.sum
View file

@ -72,8 +72,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/kubelogin v0.2.15 h1:oJqD8Dvput3rO/xZgMTU+hBrcgg0BfQGPCNHJ2dEmys=
github.com/Azure/kubelogin v0.2.15/go.mod h1:RwJS8TzSHTVQhfIZA4HLS79QGfvIp0ocIVLT5oHS/ls=
github.com/Azure/kubelogin v0.2.16 h1:z0jwNQ9A7LvIqS0Go+6CPZv0TuQQRL2mc+zY9wjBuF8=
github.com/Azure/kubelogin v0.2.16/go.mod h1:UvizZ5Gu/2btUFXm2cccbxliK/ensgBD5NTCihZoONE=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
@ -124,8 +124,6 @@ github.com/argoproj/pkg/v2 v2.0.1/go.mod h1:sdifF6sUTx9ifs38ZaiNMRJuMpSCBB9GulHf
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go v1.44.39/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/config v1.32.11 h1:ftxI5sgz8jZkckuUHXfC/wMUc8u3fG1vQS0plr2F2Zs=
@ -140,10 +138,14 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJ
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.10 h1:hAK/gb7zTE3zIU3tKuG1+62NHyQ+03Ew1YiJZVNmd04=
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.10/go.mod h1:UgLONgXRd26X5Zz/RG29duwo+mK5ZbqSRGOs/sVl6Uw=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 h1:X1Tow7suZk9UCJHE1Iw9GMZJJl0dAnKXXP1NaSDHwmw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.8 h1:mGgiunl7ZwOwhpJwJNF4JfsZFYJp08wjyS3NqFQe3ws=
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.8/go.mod h1:KdM2EhXeHfeBQz5keOvv/FM7kbesjCWm7HEEyJe3frs=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
github.com/aws/aws-sdk-go-v2/service/sqs v1.38.1 h1:ZtgZeMPJH8+/vNs9vJFFLI0QEzYbcN0p7x1/FFwyROc=
@ -168,8 +170,8 @@ github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bombsimon/logrusr/v4 v4.1.0 h1:uZNPbwusB0eUXlO8hIUwStE6Lr5bLN6IgYgG+75kuh4=
github.com/bombsimon/logrusr/v4 v4.1.0/go.mod h1:pjfHC5e59CvjTBIU3V3sGhFWFAnsnhOR03TRc6im0l8=
github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 h1:SmbUK/GxpAspRjSQbB6ARvH+ArzlNzTtHydNyXUQ6zg=
github.com/bradleyfalzon/ghinstallation/v2 v2.17.0/go.mod h1:vuD/xvJT9Y+ZVZRv4HQ42cMyPFIYqpc7AbB4Gvt/DlY=
github.com/bradleyfalzon/ghinstallation/v2 v2.18.0 h1:WPqnN6NS9XvYlOgZQAIseN7Z1uAiE+UxgDKlW7FvFuU=
github.com/bradleyfalzon/ghinstallation/v2 v2.18.0/go.mod h1:gpoSwwWc4biE49F7n+roCcpkEkZ1Qr9soZ2ESvMiouU=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@ -473,8 +475,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE=
github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM=
github.com/google/go-github/v75 v75.0.0 h1:k7q8Bvg+W5KxRl9Tjq16a9XEgVY1pwuiG5sIL7435Ic=
github.com/google/go-github/v75 v75.0.0/go.mod h1:H3LUJEA1TCrzuUqtdAQniBNwuKiQIqdGKgBo1/M/uqI=
github.com/google/go-github/v84 v84.0.0 h1:I/0Xn5IuChMe8TdmI2bbim5nyhaRFJ7DEdzmD2w+yVA=
github.com/google/go-github/v84 v84.0.0/go.mod h1:WwYL1z1ajRdlaPszjVu/47x1L0PXukJBn73xsiYrRRQ=
github.com/google/go-jsonnet v0.21.0 h1:43Bk3K4zMRP/aAZm9Po2uSEjY6ALCkYUVIcz9HLGMvA=
github.com/google/go-jsonnet v0.21.0/go.mod h1:tCGAu8cpUpEZcdGMmdOu37nh8bGgqubhI5v2iSk3KJQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
@ -583,9 +585,7 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jeremywohl/flatten v1.0.2-0.20211013061545-07e4a09fb8e4 h1:4mRgApcowAtxNLwOQ93jhHMLFgkX2D5yM53mtZSk6Nw=
github.com/jeremywohl/flatten v1.0.2-0.20211013061545-07e4a09fb8e4/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
@ -1030,8 +1030,8 @@ golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -1074,8 +1074,8 @@ golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1141,8 +1141,8 @@ golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1250,8 +1250,8 @@ golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -1278,8 +1278,8 @@ golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1304,8 +1304,8 @@ golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View file

@ -48,6 +48,12 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT
valueFrom:
configMapKeyRef:
key: notificationscontroller.processors.count
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_LOG_FORMAT_TIMESTAMP
valueFrom:
configMapKeyRef:
@ -96,4 +102,4 @@ spec:
seccompProfile:
type: RuntimeDefault
nodeSelector:
kubernetes.io/os: linux
kubernetes.io/os: linux

View file

@ -33129,6 +33129,12 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT
valueFrom:
configMapKeyRef:
key: notificationscontroller.processors.count
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_LOG_FORMAT_TIMESTAMP
valueFrom:
configMapKeyRef:

View file

@ -32959,6 +32959,12 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT
valueFrom:
configMapKeyRef:
key: notificationscontroller.processors.count
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_LOG_FORMAT_TIMESTAMP
valueFrom:
configMapKeyRef:

View file

@ -2376,6 +2376,12 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT
valueFrom:
configMapKeyRef:
key: notificationscontroller.processors.count
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_LOG_FORMAT_TIMESTAMP
valueFrom:
configMapKeyRef:

View file

@ -2206,6 +2206,12 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT
valueFrom:
configMapKeyRef:
key: notificationscontroller.processors.count
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_LOG_FORMAT_TIMESTAMP
valueFrom:
configMapKeyRef:

View file

@ -32147,6 +32147,12 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT
valueFrom:
configMapKeyRef:
key: notificationscontroller.processors.count
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_LOG_FORMAT_TIMESTAMP
valueFrom:
configMapKeyRef:

View file

@ -31975,6 +31975,12 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT
valueFrom:
configMapKeyRef:
key: notificationscontroller.processors.count
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_LOG_FORMAT_TIMESTAMP
valueFrom:
configMapKeyRef:

View file

@ -1394,6 +1394,12 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT
valueFrom:
configMapKeyRef:
key: notificationscontroller.processors.count
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_LOG_FORMAT_TIMESTAMP
valueFrom:
configMapKeyRef:

View file

@ -1222,6 +1222,12 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT
valueFrom:
configMapKeyRef:
key: notificationscontroller.processors.count
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_LOG_FORMAT_TIMESTAMP
valueFrom:
configMapKeyRef:

View file

@ -21,6 +21,12 @@
],
"enabled": true
},
{
"description": "Ignore gitops-engine: local replace (./gitops-engine), digest bumps are not applicable.",
"matchDatasources": ["go"],
"matchPackageNames": ["github.com/argoproj/argo-cd/gitops-engine"],
"enabled": false
},
{
"description": "Run make mockgen after updating mockery",
"matchDatasources": [

View file

@ -1583,14 +1583,15 @@ func (server *ArgoCDServer) getClaims(ctx context.Context) (jwt.Claims, string,
}
finalClaims := claims
if server.settings.IsSSOConfigured() {
oidcConfig := server.settings.OIDCConfig()
if oidcConfig != nil || server.settings.IsDexConfigured() {
updatedClaims, err := server.ssoClientApp.SetGroupsFromUserInfo(ctx, claims, util_session.SessionManagerClaimsIssuer)
if err != nil {
return claims, "", status.Errorf(codes.Unauthenticated, "invalid session: %v", err)
}
finalClaims = updatedClaims
// OIDC tokens are automatically refreshed here prior to expiration
refreshedToken, err := server.ssoClientApp.CheckAndRefreshToken(ctx, updatedClaims, server.settings.OIDCRefreshTokenThreshold)
refreshedToken, err := server.ssoClientApp.CheckAndRefreshToken(ctx, updatedClaims, server.settings.RefreshTokenThresholdWithConfig(oidcConfig))
if err != nil {
log.Errorf("error checking and refreshing token: %v", err)
}

View file

@ -8,7 +8,7 @@ RUN ln -s /usr/lib/$(uname -m)-linux-gnu /usr/lib/linux-gnu
# Please make sure to also check the contained yarn version and update the references below when upgrading this image's version
FROM docker.io/library/node:22.9.0@sha256:8398ea18b8b72817c84af283f72daed9629af2958c4f618fe6db4f453c5c9328 AS node
FROM docker.io/library/golang:1.26.1@sha256:e2ddb153f786ee6210bf8c40f7f35490b3ff7d38be70d1a0d358ba64225f6428 AS golang
FROM docker.io/library/golang:1.26.1@sha256:c7e98cc0fd4dfb71ee7465fee6c9a5f079163307e4bf141b336bb9dae00159a5 AS golang
FROM docker.io/library/registry:3.0@sha256:6c5666b861f3505b116bb9aa9b25175e71210414bd010d92035ff64018f9457e AS registry

View file

@ -3087,11 +3087,9 @@ func TestDeletionConfirmation(t *testing.T) {
t.Context(), &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "test-configmap",
Labels: map[string]string{
common.LabelKeyAppInstance: ctx.AppName(),
},
Annotations: map[string]string{
AnnotationSyncOptions: "Prune=confirm",
common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/test-configmap", ctx.GetName(), ctx.DeploymentNamespace()),
AnnotationSyncOptions: "Prune=confirm",
},
},
}, metav1.CreateOptions{})
@ -3102,7 +3100,8 @@ func TestDeletionConfirmation(t *testing.T) {
When().
PatchFile("guestbook-ui-deployment.yaml", `[{ "op": "add", "path": "/metadata/annotations", "value": { "argocd.argoproj.io/sync-options": "Delete=confirm" }}]`).
CreateApp().Sync().
Then().ExpectConsistently(OperationPhaseIs(OperationRunning), time.Second, 5*time.Second).
Then().
ExpectConsistently(OperationPhaseIs(OperationRunning), time.Second, 5*time.Second).
When().ConfirmDeletion().
Then().Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).

View file

@ -443,9 +443,10 @@ func (a *Actions) ConfirmDeletion() *Actions {
a.runCli("app", "confirm-deletion", a.context.AppQualifiedName())
// Always sleep more than a second after the confirmation so the timestamp
// is not valid for immediate subsequent operations
time.Sleep(1500 * time.Millisecond)
// Always sleep a few seconds after the confirmation so the timestamp
// is not valid for immediate subsequent operations.
// Kubernetes containers may have clocks with a few seconds difference, and we want to avoid race conditions.
time.Sleep(3 * time.Second)
return a
}

View file

@ -58,18 +58,21 @@ func (c *Consequences) Expect(e Expectation) *Consequences {
return c
}
// ExpectConsistently will continuously evaluate a condition, and it must be true each time it is evaluated, otherwise the test is failed. The condition will be repeatedly evaluated until 'expirationDuration' is met, waiting 'waitDuration' after each success.
// ExpectConsistently will continuously evaluate a condition. Once true, it must be true each time it is evaluated, otherwise the test is failed.
// The condition will be repeatedly evaluated once it is true,until 'expirationDuration' is met, waiting 'waitDuration' after each success.
func (c *Consequences) ExpectConsistently(e Expectation, waitDuration time.Duration, expirationDuration time.Duration) *Consequences {
// this invocation makes sure this func is not reported as the cause of the failure - we are a "test helper"
c.context.T().Helper()
c.Expect(e) // ensure the condition is true before expecting consistency
expiration := time.Now().Add(expirationDuration)
for time.Now().Before(expiration) {
state, message := e(c)
switch state {
case succeeded:
log.Infof("expectation succeeded: %s", message)
case failed:
default:
c.context.T().Fatalf("failed expectation: %s", message)
return c
}

View file

@ -167,7 +167,6 @@ func TestAutomatedSelfHealingAgainstAnnotatedTag(t *testing.T) {
app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{Prune: true, SelfHeal: false}}
}).
Then().
Expect(SyncStatusIs(SyncStatusCodeSynced)).
ExpectConsistently(SyncStatusIs(SyncStatusCodeSynced), WaitDuration, time.Second*10).
When().
// Update the annotated tag to a new git commit, that has a new revisionHistoryLimit.
@ -220,7 +219,6 @@ func TestAutomatedSelfHealingAgainstLightweightTag(t *testing.T) {
app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{Prune: true, SelfHeal: false}}
}).
Then().
Expect(SyncStatusIs(SyncStatusCodeSynced)).
ExpectConsistently(SyncStatusIs(SyncStatusCodeSynced), WaitDuration, time.Second*10).
When().
// Update the annotated tag to a new git commit, that has a new revisionHistoryLimit.

View file

@ -1,6 +1,6 @@
ARG BASE_IMAGE=docker.io/library/ubuntu:25.10@sha256:4a9232cc47bf99defcc8860ef6222c99773330367fcecbf21ba2edb0b810a31e
FROM docker.io/library/golang:1.26.0@sha256:c83e68f3ebb6943a2904fa66348867d108119890a2c6a2e6f07b38d0eb6c25c5 AS go
FROM docker.io/library/golang:1.26.0@sha256:fb612b7831d53a89cbc0aaa7855b69ad7b0caf603715860cf538df854d047b84 AS go
RUN go install github.com/mattn/goreman@latest && \
go install github.com/kisielk/godepgraph@latest

View file

@ -216,7 +216,7 @@ func NewClientApp(settings *settings.ArgoCDSettings, dexServerAddr string, dexTL
clientCache: cacheClient,
azure: azureApp{mtx: &sync.RWMutex{}},
domainHint: domainHint,
refreshTokenThreshold: settings.OIDCRefreshTokenThreshold,
refreshTokenThreshold: settings.RefreshTokenThreshold(),
}
log.Infof("Creating client app (%s)", a.clientID)
u, err := url.Parse(settings.URL)

View file

@ -136,9 +136,6 @@ type ArgoCDSettings struct {
// token verification to pass despite the OIDC provider having an invalid certificate. Only set to `true` if you
// understand the risks.
OIDCTLSInsecureSkipVerify bool `json:"oidcTLSInsecureSkipVerify"`
// OIDCRefreshTokenThreshold sets the threshold for preemptive server-side token refresh. If set to 0, tokens
// will not be refreshed and will expire before client is redirected to login.
OIDCRefreshTokenThreshold time.Duration `json:"oidcRefreshTokenThreshold,omitempty"`
// AppsInAnyNamespaceEnabled indicates whether applications are allowed to be created in any namespace
AppsInAnyNamespaceEnabled bool `json:"appsInAnyNamespaceEnabled"`
// ExtensionConfig configurations related to ArgoCD proxy extensions. The keys are the extension name.
@ -1484,7 +1481,6 @@ func getDownloadBinaryUrlsFromConfigMap(argoCDCM *corev1.ConfigMap) map[string]s
func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *corev1.ConfigMap) {
settings.DexConfig = argoCDCM.Data[settingDexConfigKey]
settings.OIDCConfigRAW = argoCDCM.Data[settingsOIDCConfigKey]
settings.OIDCRefreshTokenThreshold = settings.RefreshTokenThreshold()
settings.KustomizeBuildOptions = argoCDCM.Data[kustomizeBuildOptionsKey]
settings.StatusBadgeEnabled = argoCDCM.Data[statusBadgeEnabledKey] == "true"
settings.StatusBadgeRootUrl = argoCDCM.Data[statusBadgeRootURLKey]
@ -1937,7 +1933,12 @@ func (a *ArgoCDSettings) UserInfoCacheExpiration() time.Duration {
// RefreshTokenThreshold returns the duration before token expiration that a token should be refreshed by the server
func (a *ArgoCDSettings) RefreshTokenThreshold() time.Duration {
if oidcConfig := a.OIDCConfig(); oidcConfig != nil && oidcConfig.RefreshTokenThreshold != "" {
return a.RefreshTokenThresholdWithConfig(a.OIDCConfig())
}
// RefreshTokenThresholdWithConfig takes oidcConfig as param and returns the duration before token expiration that a token should be refreshed by the server
func (a *ArgoCDSettings) RefreshTokenThresholdWithConfig(oidcConfig *OIDCConfig) time.Duration {
if oidcConfig != nil && oidcConfig.RefreshTokenThreshold != "" {
refreshTokenThreshold, err := time.ParseDuration(oidcConfig.RefreshTokenThreshold)
if err != nil {
log.Warnf("Failed to parse 'oidc.config.refreshTokenThreshold' key: %v", err)