@@ -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
@@ -1789,3 +1793,146 @@ func TestReconcilerUpdate_EnsureControllerTagsError(t *testing.T) {
17891793 rm .AssertNotCalled (t , "LateInitialize" , ctx , latest )
17901794 rm .AssertCalled (t , "EnsureTags" , ctx , desired , scmd )
17911795}
1796+
1797+ func TestReconcile_AccountDrifted (t * testing.T ) {
1798+ require := require .New (t )
1799+
1800+ ctx := context .TODO ()
1801+ req := ctrlrt.Request {
1802+ NamespacedName : types.NamespacedName {
1803+ Namespace : "production" ,
1804+ Name : "mybook" ,
1805+ },
1806+ }
1807+
1808+ // Create resource with existing account
1809+ existingAccount := ackv1alpha1 .AWSAccountID ("111111111111" )
1810+
1811+ desired , _ , metaObj := resourceMocks ()
1812+ metaObj .SetNamespace ("production" )
1813+
1814+ ids := & ackmocks.AWSResourceIdentifiers {}
1815+ ids .On ("Region" ).Return (nil )
1816+ ids .On ("OwnerAccountID" ).Return (& existingAccount )
1817+ desired .On ("Identifiers" ).Return (ids )
1818+ desired .On ("Conditions" ).Return ([]* ackv1alpha1.Condition {})
1819+ desired .On (
1820+ "ReplaceConditions" ,
1821+ mock .AnythingOfType ("[]*v1alpha1.Condition" ),
1822+ ).Return ()
1823+ desired .On ("IsBeingDeleted" ).Return (false )
1824+
1825+ // Setup resource descriptor
1826+ rd := & ackmocks.AWSResourceDescriptor {}
1827+ rd .On ("GroupVersionKind" ).Return (schema.GroupVersionKind {
1828+ Group : "test.services.k8s.aws" ,
1829+ Kind : "Book" ,
1830+ Version : "v1alpha1" ,
1831+ })
1832+ rd .On ("EmptyRuntimeObject" ).Return (& fakeBook {})
1833+ rd .On ("ResourceFromRuntimeObject" , mock .Anything ).Return (desired )
1834+
1835+ // Setup service controller
1836+ sc := & ackmocks.ServiceController {}
1837+ sc .On ("GetMetadata" ).Return (acktypes.ServiceControllerMetadata {})
1838+ sc .On ("NewAWSConfig" ,
1839+ mock .Anything ,
1840+ mock .AnythingOfType ("v1alpha1.AWSRegion" ),
1841+ mock .Anything ,
1842+ mock .AnythingOfType ("v1alpha1.AWSResourceName" ),
1843+ mock .AnythingOfType ("schema.GroupVersionKind" ),
1844+ ).Return (aws.Config {}, nil )
1845+
1846+ // Get fakeLogger
1847+ zapOptions := ctrlrtzap.Options {
1848+ Development : true ,
1849+ Level : zapcore .InfoLevel ,
1850+ }
1851+ fakeLogger := ctrlrtzap .New (ctrlrtzap .UseFlagOptions (& zapOptions ))
1852+
1853+ // Create fake k8s client with namespace that has owner account annotation
1854+ k8sClient := k8sfake .NewSimpleClientset ()
1855+
1856+ // Create namespace with owner account annotation
1857+ namespace := & corev1.Namespace {
1858+ ObjectMeta : metav1.ObjectMeta {
1859+ Name : "production" ,
1860+ Annotations : map [string ]string {
1861+ ackv1alpha1 .AnnotationOwnerAccountID : "222222222222" ,
1862+ },
1863+ },
1864+ }
1865+ k8sClient .CoreV1 ().Namespaces ().Create (context .Background (), namespace , metav1.CreateOptions {})
1866+
1867+ // Create CARM configmap
1868+ configMap := & corev1.ConfigMap {
1869+ ObjectMeta : metav1.ObjectMeta {
1870+ Name : ackrtcache .ACKRoleAccountMap ,
1871+ Namespace : "ack-system" ,
1872+ },
1873+ Data : map [string ]string {
1874+ "222222222222" : "arn:aws:iam::222222222222:role/ACKRole" ,
1875+ },
1876+ }
1877+ k8sClient .CoreV1 ().ConfigMaps ("ack-system" ).Create (context .Background (), configMap , metav1.CreateOptions {})
1878+
1879+ // Create caches with the k8s client
1880+ caches := ackrtcache .New (fakeLogger , ackrtcache.Config {}, featuregate.FeatureGates {})
1881+
1882+ // Run the caches
1883+ stopCh := make (chan struct {})
1884+ defer close (stopCh )
1885+ caches .Run (k8sClient )
1886+
1887+ // Wait for caches to sync
1888+ time .Sleep (100 * time .Millisecond )
1889+
1890+ kc := & ctrlrtclientmock.Client {}
1891+ statusWriter := & ctrlrtclientmock.SubResourceWriter {}
1892+ kc .On ("Status" ).Return (statusWriter )
1893+ statusWriter .On ("Patch" , mock .Anything , mock .Anything , mock .Anything ).Return (nil )
1894+
1895+ rm := & ackmocks.AWSResourceManager {}
1896+ rmf := & ackmocks.AWSResourceManagerFactory {}
1897+ rmf .On ("ResourceDescriptor" ).Return (rd )
1898+ rmf .On ("ManagerFor" ,
1899+ mock .Anything ,
1900+ mock .Anything ,
1901+ mock .Anything ,
1902+ mock .Anything ,
1903+ mock .Anything ,
1904+ mock .AnythingOfType ("v1alpha1.AWSAccountID" ),
1905+ mock .AnythingOfType ("v1alpha1.AWSRegion" ),
1906+ mock .AnythingOfType ("v1alpha1.AWSResourceName" ),
1907+ ).Return (rm , nil )
1908+ rm .On ("ResolveReferences" , mock .Anything , mock .Anything , mock .Anything ).Return (
1909+ desired , false , nil ,
1910+ )
1911+ rm .On ("EnsureTags" , mock .Anything , mock .Anything , mock .Anything ).Return (nil )
1912+
1913+ // Create reconciler with namespace cache
1914+ r := & resourceReconciler {
1915+ reconciler : reconciler {
1916+ kc : kc ,
1917+ sc : sc ,
1918+ log : fakeLogger ,
1919+ cfg : ackcfg.Config {AccountID : "333333333333" },
1920+ cache : caches ,
1921+ metrics : ackmetrics .NewMetrics ("test" ),
1922+ },
1923+ rmf : rmf ,
1924+ rd : rd ,
1925+ }
1926+
1927+ apiReader := & ctrlrtclientmock.Reader {}
1928+ apiReader .On ("Get" , ctx , req .NamespacedName , mock .AnythingOfType ("*runtime.fakeBook" )).Return (nil )
1929+ r .apiReader = apiReader
1930+
1931+ // Call Reconcile
1932+ _ , err := r .Reconcile (ctx , req )
1933+
1934+ // Should get terminal error for account drift
1935+ require .NotNil (err )
1936+ assert .Contains (t , err .Error (), "Resource already exists in account 111111111111" )
1937+ assert .Contains (t , err .Error (), "but the role used for reconciliation is in account 222222222222" )
1938+ }
0 commit comments