@@ -21,6 +21,7 @@ import (
2121 "testing"
2222 "time"
2323
24+ "github.com/aws/aws-sdk-go-v2/aws"
2425 "github.com/aws/smithy-go"
2526 "github.com/stretchr/testify/assert"
2627 "github.com/stretchr/testify/mock"
@@ -29,10 +30,17 @@ import (
2930 corev1 "k8s.io/api/core/v1"
3031 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3132 k8sobj "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33+ "k8s.io/apimachinery/pkg/runtime/schema"
3234 k8srtschema "k8s.io/apimachinery/pkg/runtime/schema"
35+ "k8s.io/apimachinery/pkg/types"
36+ k8sfake "k8s.io/client-go/kubernetes/fake"
37+ ctrlrt "sigs.k8s.io/controller-runtime"
3338 ctrlrtzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
3439
3540 ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
41+ k8srtschemamocks "github.com/aws-controllers-k8s/runtime/mocks/apimachinery/pkg/runtime/schema"
42+ ctrlrtclientmock "github.com/aws-controllers-k8s/runtime/mocks/controller-runtime/pkg/client"
43+ ackmocks "github.com/aws-controllers-k8s/runtime/mocks/pkg/types"
3644 ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
3745 ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition"
3846 ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config"
@@ -42,10 +50,6 @@ import (
4250 "github.com/aws-controllers-k8s/runtime/pkg/requeue"
4351 ackrtcache "github.com/aws-controllers-k8s/runtime/pkg/runtime/cache"
4452 acktypes "github.com/aws-controllers-k8s/runtime/pkg/types"
45-
46- k8srtschemamocks "github.com/aws-controllers-k8s/runtime/mocks/apimachinery/pkg/runtime/schema"
47- ctrlrtclientmock "github.com/aws-controllers-k8s/runtime/mocks/controller-runtime/pkg/client"
48- ackmocks "github.com/aws-controllers-k8s/runtime/mocks/pkg/types"
4953)
5054
5155// isWithoutCancelContext checks if the context is a WithoutCancel context
@@ -503,7 +507,7 @@ func TestReconcilerAdoptOrCreateResource_Adopt(t *testing.T) {
503507 latest , latestRTObj , latestMetaObj := resourceMocks ()
504508 latest .On ("Identifiers" ).Return (ids )
505509 latest .On ("Conditions" ).Return ([]* ackv1alpha1.Condition {})
506- latest .On (
510+ latest .On (
507511 "ReplaceConditions" ,
508512 mock .AnythingOfType ("[]*v1alpha1.Condition" ),
509513 ).Return ().Run (func (args mock.Arguments ) {
@@ -1746,3 +1750,146 @@ func TestReconcilerUpdate_EnsureControllerTagsError(t *testing.T) {
17461750 rm .AssertNotCalled (t , "LateInitialize" , ctx , latest )
17471751 rm .AssertCalled (t , "EnsureTags" , ctx , desired , scmd )
17481752}
1753+
1754+ func TestReconcile_AccountDrifted (t * testing.T ) {
1755+ require := require .New (t )
1756+
1757+ ctx := context .TODO ()
1758+ req := ctrlrt.Request {
1759+ NamespacedName : types.NamespacedName {
1760+ Namespace : "production" ,
1761+ Name : "mybook" ,
1762+ },
1763+ }
1764+
1765+ // Create resource with existing account
1766+ existingAccount := ackv1alpha1 .AWSAccountID ("111111111111" )
1767+
1768+ desired , _ , metaObj := resourceMocks ()
1769+ metaObj .SetNamespace ("production" )
1770+
1771+ ids := & ackmocks.AWSResourceIdentifiers {}
1772+ ids .On ("Region" ).Return (nil )
1773+ ids .On ("OwnerAccountID" ).Return (& existingAccount )
1774+ desired .On ("Identifiers" ).Return (ids )
1775+ desired .On ("Conditions" ).Return ([]* ackv1alpha1.Condition {})
1776+ desired .On (
1777+ "ReplaceConditions" ,
1778+ mock .AnythingOfType ("[]*v1alpha1.Condition" ),
1779+ ).Return ()
1780+ desired .On ("IsBeingDeleted" ).Return (false )
1781+
1782+ // Setup resource descriptor
1783+ rd := & ackmocks.AWSResourceDescriptor {}
1784+ rd .On ("GroupVersionKind" ).Return (schema.GroupVersionKind {
1785+ Group : "test.services.k8s.aws" ,
1786+ Kind : "Book" ,
1787+ Version : "v1alpha1" ,
1788+ })
1789+ rd .On ("EmptyRuntimeObject" ).Return (& fakeBook {})
1790+ rd .On ("ResourceFromRuntimeObject" , mock .Anything ).Return (desired )
1791+
1792+ // Setup service controller
1793+ sc := & ackmocks.ServiceController {}
1794+ sc .On ("GetMetadata" ).Return (acktypes.ServiceControllerMetadata {})
1795+ sc .On ("NewAWSConfig" ,
1796+ mock .Anything ,
1797+ mock .AnythingOfType ("v1alpha1.AWSRegion" ),
1798+ mock .Anything ,
1799+ mock .AnythingOfType ("v1alpha1.AWSResourceName" ),
1800+ mock .AnythingOfType ("schema.GroupVersionKind" ),
1801+ ).Return (aws.Config {}, nil )
1802+
1803+ // Get fakeLogger
1804+ zapOptions := ctrlrtzap.Options {
1805+ Development : true ,
1806+ Level : zapcore .InfoLevel ,
1807+ }
1808+ fakeLogger := ctrlrtzap .New (ctrlrtzap .UseFlagOptions (& zapOptions ))
1809+
1810+ // Create fake k8s client with namespace that has owner account annotation
1811+ k8sClient := k8sfake .NewSimpleClientset ()
1812+
1813+ // Create namespace with owner account annotation
1814+ namespace := & corev1.Namespace {
1815+ ObjectMeta : metav1.ObjectMeta {
1816+ Name : "production" ,
1817+ Annotations : map [string ]string {
1818+ ackv1alpha1 .AnnotationOwnerAccountID : "222222222222" ,
1819+ },
1820+ },
1821+ }
1822+ k8sClient .CoreV1 ().Namespaces ().Create (context .Background (), namespace , metav1.CreateOptions {})
1823+
1824+ // Create CARM configmap
1825+ configMap := & corev1.ConfigMap {
1826+ ObjectMeta : metav1.ObjectMeta {
1827+ Name : ackrtcache .ACKRoleAccountMap ,
1828+ Namespace : "ack-system" ,
1829+ },
1830+ Data : map [string ]string {
1831+ "222222222222" : "arn:aws:iam::222222222222:role/ACKRole" ,
1832+ },
1833+ }
1834+ k8sClient .CoreV1 ().ConfigMaps ("ack-system" ).Create (context .Background (), configMap , metav1.CreateOptions {})
1835+
1836+ // Create caches with the k8s client
1837+ caches := ackrtcache .New (fakeLogger , ackrtcache.Config {}, featuregate.FeatureGates {})
1838+
1839+ // Run the caches
1840+ stopCh := make (chan struct {})
1841+ defer close (stopCh )
1842+ caches .Run (k8sClient )
1843+
1844+ // Wait for caches to sync
1845+ time .Sleep (100 * time .Millisecond )
1846+
1847+ kc := & ctrlrtclientmock.Client {}
1848+ statusWriter := & ctrlrtclientmock.SubResourceWriter {}
1849+ kc .On ("Status" ).Return (statusWriter )
1850+ statusWriter .On ("Patch" , mock .Anything , mock .Anything , mock .Anything ).Return (nil )
1851+
1852+ rm := & ackmocks.AWSResourceManager {}
1853+ rmf := & ackmocks.AWSResourceManagerFactory {}
1854+ rmf .On ("ResourceDescriptor" ).Return (rd )
1855+ rmf .On ("ManagerFor" ,
1856+ mock .Anything ,
1857+ mock .Anything ,
1858+ mock .Anything ,
1859+ mock .Anything ,
1860+ mock .Anything ,
1861+ mock .AnythingOfType ("v1alpha1.AWSAccountID" ),
1862+ mock .AnythingOfType ("v1alpha1.AWSRegion" ),
1863+ mock .AnythingOfType ("v1alpha1.AWSResourceName" ),
1864+ ).Return (rm , nil )
1865+ rm .On ("ResolveReferences" , mock .Anything , mock .Anything , mock .Anything ).Return (
1866+ desired , false , nil ,
1867+ )
1868+ rm .On ("EnsureTags" , mock .Anything , mock .Anything , mock .Anything ).Return (nil )
1869+
1870+ // Create reconciler with namespace cache
1871+ r := & resourceReconciler {
1872+ reconciler : reconciler {
1873+ kc : kc ,
1874+ sc : sc ,
1875+ log : fakeLogger ,
1876+ cfg : ackcfg.Config {AccountID : "333333333333" },
1877+ cache : caches ,
1878+ metrics : ackmetrics .NewMetrics ("test" ),
1879+ },
1880+ rmf : rmf ,
1881+ rd : rd ,
1882+ }
1883+
1884+ apiReader := & ctrlrtclientmock.Reader {}
1885+ apiReader .On ("Get" , ctx , req .NamespacedName , mock .AnythingOfType ("*runtime.fakeBook" )).Return (nil )
1886+ r .apiReader = apiReader
1887+
1888+ // Call Reconcile
1889+ _ , err := r .Reconcile (ctx , req )
1890+
1891+ // Should get terminal error for account drift
1892+ require .NotNil (err )
1893+ assert .Contains (t , err .Error (), "Resource already exists in account 111111111111" )
1894+ assert .Contains (t , err .Error (), "but the role used for reconciliation is in account 222222222222" )
1895+ }
0 commit comments