diff --git a/.gitignore b/.gitignore index 47c3b72f3..dce203432 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ __azurite* go.work.sum # Taco specific binaries -taco + statesman terraform-provider-opentaco opentacosvc @@ -35,3 +35,6 @@ bin/ *.swp *.swo *~ + +#data +data/ \ No newline at end of file diff --git a/go.mod b/go.mod index 02d13b8c8..3c0f6635c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,28 @@ module github.com/diggerhq/digger go 1.24.0 + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/kelseyhightower/envconfig v1.4.0 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/microsoft/go-mssqldb v1.8.2 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/text v0.21.0 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.0 // indirect + gorm.io/driver/sqlite v1.6.0 // indirect + gorm.io/driver/sqlserver v1.6.1 // indirect + gorm.io/gorm v1.31.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..1596e40e1 --- /dev/null +++ b/go.sum @@ -0,0 +1,210 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I= +github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/driver/sqlserver v1.6.1 h1:XWISFsu2I2pqd1KJhhTZNJMx1jNQ+zVL/Q8ovDcUjtY= +gorm.io/driver/sqlserver v1.6.1/go.mod h1:VZeNn7hqX1aXoN5TPAFGWvxWG90xtA8erGn2gQmpc6U= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= +gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/taco/cmd/statesman/go.mod b/taco/cmd/statesman/go.mod index ab1e60d3a..30a7eca15 100644 --- a/taco/cmd/statesman/go.mod +++ b/taco/cmd/statesman/go.mod @@ -4,10 +4,12 @@ go 1.24 require ( github.com/diggerhq/digger/opentaco/internal v0.0.0 + github.com/kelseyhightower/envconfig v1.4.0 github.com/labstack/echo/v4 v4.11.4 ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/aws/aws-sdk-go-v2 v1.38.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect github.com/aws/aws-sdk-go-v2/config v1.31.2 // indirect @@ -27,16 +29,25 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect github.com/aws/smithy-go v1.22.5 // indirect github.com/coreos/go-oidc/v3 v3.11.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect - github.com/google/go-cmp v0.7.0 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/google/jsonapi v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/microsoft/go-mssqldb v1.8.2 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/testify v1.10.0 // indirect @@ -45,9 +56,15 @@ require ( golang.org/x/crypto v0.32.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.7.0 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.0 // indirect + gorm.io/driver/sqlite v1.6.0 // indirect + gorm.io/driver/sqlserver v1.6.1 // indirect + gorm.io/gorm v1.31.0 // indirect ) replace github.com/diggerhq/digger/opentaco/internal => ../../internal diff --git a/taco/cmd/statesman/go.sum b/taco/cmd/statesman/go.sum index b76f29876..4abccac69 100644 --- a/taco/cmd/statesman/go.sum +++ b/taco/cmd/statesman/go.sum @@ -1,3 +1,23 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8= github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg= @@ -36,17 +56,67 @@ github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/jsonapi v1.0.0 h1:qIGgO5Smu3yJmSs+QlvhQnrscdZfFhiV6S8ryJAglqU= github.com/google/jsonapi v1.0.0/go.mod h1:YYHiRPJT8ARXGER8In9VuLv4qvLfDmA9ULQqptbLE4s= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -56,23 +126,170 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I= +github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/driver/sqlserver v1.6.1 h1:XWISFsu2I2pqd1KJhhTZNJMx1jNQ+zVL/Q8ovDcUjtY= +gorm.io/driver/sqlserver v1.6.1/go.mod h1:VZeNn7hqX1aXoN5TPAFGWvxWG90xtA8erGn2gQmpc6U= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= +gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/taco/cmd/statesman/main.go b/taco/cmd/statesman/main.go index 0122c1180..1ca7c24e9 100644 --- a/taco/cmd/statesman/main.go +++ b/taco/cmd/statesman/main.go @@ -21,15 +21,20 @@ import ( "github.com/diggerhq/digger/opentaco/internal/analytics" "github.com/diggerhq/digger/opentaco/internal/api" + "github.com/diggerhq/digger/opentaco/internal/auth" + "github.com/diggerhq/digger/opentaco/internal/query" + "github.com/diggerhq/digger/opentaco/internal/queryfactory" "github.com/diggerhq/digger/opentaco/internal/storage" + "github.com/diggerhq/digger/opentaco/internal/wiring" + "github.com/kelseyhightower/envconfig" "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" + echomiddleware "github.com/labstack/echo/v4/middleware" ) func main() { var ( port = flag.String("port", "8080", "Server port") - authDisable = flag.Bool("auth-disable", false, "Disable auth enforcement (default: false)") + authDisable = flag.Bool("auth-disable", os.Getenv("OPENTACO_AUTH_DISABLE") == "true", "Disable auth enforcement (default: false)") storageType = flag.String("storage", "s3", "Storage type: s3 or memory (default: s3 with fallback to memory)") s3Bucket = flag.String("s3-bucket", os.Getenv("OPENTACO_S3_BUCKET"), "S3 bucket for state storage") s3Prefix = flag.String("s3-prefix", os.Getenv("OPENTACO_S3_PREFIX"), "S3 key prefix (optional)") @@ -37,32 +42,112 @@ func main() { ) flag.Parse() + // Load configuration from environment variables into our struct. + var queryCfg query.Config + err := envconfig.Process("taco", &queryCfg) // The prefix "TACO" will be used for all vars. + if err != nil { + log.Fatalf("Failed to process configuration: %v", err) + } + + // --- Initialize Stores --- + + // Create the database index store using the dedicated factory. + queryStore, err := queryfactory.NewQueryStore(queryCfg) + if err != nil { + log.Fatalf("Failed to initialize query backend: %v", err) + } + defer queryStore.Close() + + log.Printf("Query backend initialized: %s (enabled: %v)", queryCfg.Backend, queryStore.IsEnabled()) + + if queryStore.IsEnabled(){ + log.Println("Query backend enabled successfully") + }else{ + log.Println("Query backend disabled. You are in no-op mode.") + } + + // Initialize storage - var store storage.UnitStore + var blobStore storage.UnitStore switch *storageType { case "s3": if *s3Bucket == "" { log.Printf("WARNING: S3 storage selected but bucket not provided. Falling back to in-memory storage.") - store = storage.NewMemStore() + blobStore = storage.NewMemStore() log.Printf("Using in-memory storage") break } s, err := storage.NewS3Store(context.Background(), *s3Bucket, *s3Prefix, *s3Region) if err != nil { log.Printf("WARNING: failed to initialize S3 store: %v. Falling back to in-memory storage.", err) - store = storage.NewMemStore() + blobStore = storage.NewMemStore() log.Printf("Using in-memory storage") } else { - store = s + blobStore = s log.Printf("Using S3 storage: bucket=%s prefix=%s region=%s", *s3Bucket, *s3Prefix, *s3Region) - } + + } default: - store = storage.NewMemStore() + blobStore = storage.NewMemStore() log.Printf("Using in-memory storage") } + + // 3. Create the base OrchestratingStore + orchestratingStore := storage.NewOrchestratingStore(blobStore, queryStore) + + // --- Sync RBAC Data --- + if queryStore.IsEnabled() { + if err := wiring.SyncRBACFromStorage(context.Background(), blobStore, queryStore); err != nil { + log.Printf("Warning: Failed to sync RBAC data: %v", err) + } + + // Sync existing units from storage to database + log.Println("Syncing existing units from storage to database...") + units, err := blobStore.List(context.Background(), "") + if err != nil { + log.Printf("Warning: Failed to list units from storage: %v", err) + } else { + for _, unit := range units { + // Always ensure unit exists first + if err := queryStore.SyncEnsureUnit(context.Background(), unit.ID); err != nil { + log.Printf("Warning: Failed to sync unit %s: %v", unit.ID, err) + continue + } + + // Always sync metadata to update existing records + if err := queryStore.SyncUnitMetadata(context.Background(), unit.ID, unit.Size, unit.Updated); err != nil { + log.Printf("Warning: Failed to sync metadata for unit %s: %v", unit.ID, err) + } + } + log.Printf("Synced %d units from storage to database", len(units)) + } + } + + // --- Conditionally Apply Authorization Layer with a SMART CHECK --- + var finalStore storage.UnitStore + + // Check if there are any RBAC roles defined in the database. + rbacIsConfigured, err := queryStore.HasRBACRoles(context.Background()) + if err != nil { + log.Fatalf("Failed to check for RBAC configuration: %v", err) + } + + // The condition is now two-part: Auth must be enabled AND RBAC roles must exist. + if !*authDisable && rbacIsConfigured { + log.Println("RBAC is ENABLED and CONFIGURED. Wrapping store with authorization layer.") + finalStore = storage.NewAuthorizingStore(orchestratingStore, queryStore) + } else { + if !*authDisable { + log.Println("RBAC is ENABLED but NOT CONFIGURED (no roles found). Authorization layer will be skipped.") + } else { + log.Println("RBAC is DISABLED via flag. Authorization layer will be skipped.") + } + finalStore = orchestratingStore + } + // Initialize analytics with system ID management (always create system ID) - analytics.InitGlobalWithSystemID("production", store) + analytics.InitGlobalWithSystemID("production", finalStore) // Initialize system ID synchronously during startup ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) @@ -86,14 +171,21 @@ func main() { e.HideBanner = true // Middleware - e.Use(middleware.Logger()) - e.Use(middleware.Recover()) - e.Use(middleware.RequestID()) - e.Use(middleware.Gzip()) - e.Use(middleware.Secure()) - e.Use(middleware.CORS()) + e.Use(echomiddleware.Logger()) + e.Use(echomiddleware.Recover()) + e.Use(echomiddleware.RequestID()) + e.Use(echomiddleware.Gzip()) + e.Use(echomiddleware.Secure()) + e.Use(echomiddleware.CORS()) - api.RegisterRoutes(e, store, !*authDisable) + + // Create a signer for JWTs (this may need to be configured from env vars) + signer, err := auth.NewSignerFromEnv() + if err != nil { + log.Fatalf("Failed to initialize JWT signer: %v", err) + } + + api.RegisterRoutes(e, finalStore, !*authDisable, queryStore, blobStore, signer) // Start server go func() { @@ -124,3 +216,4 @@ func main() { analytics.SendEssential("server_shutdown_complete") log.Println("Server shutdown complete") } + diff --git a/taco/cmd/taco/go.mod b/taco/cmd/taco/go.mod index bd1f23cd3..e8b9e067d 100644 --- a/taco/cmd/taco/go.mod +++ b/taco/cmd/taco/go.mod @@ -3,13 +3,32 @@ module github.com/diggerhq/digger/opentaco/cmd/taco go 1.24 require ( + github.com/diggerhq/digger/opentaco/internal v0.0.0-00010101000000-000000000000 github.com/diggerhq/digger/opentaco/pkg/sdk v0.0.0 - github.com/google/uuid v1.5.0 + github.com/google/uuid v1.6.0 github.com/mr-tron/base58 v1.2.0 github.com/spf13/cobra v1.8.0 ) require ( + github.com/aws/aws-sdk-go-v2 v1.38.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.31.2 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.4 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.87.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect + github.com/aws/smithy-go v1.22.5 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect ) diff --git a/taco/cmd/taco/go.sum b/taco/cmd/taco/go.sum index 2a4db1a6d..6c26e48c5 100644 --- a/taco/cmd/taco/go.sum +++ b/taco/cmd/taco/go.sum @@ -1,10 +1,49 @@ +github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8= +github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0/go.mod h1:/mXlTIVG9jbxkqDnr5UQNQxW1HRYxeGklkM9vAFeabg= +github.com/aws/aws-sdk-go-v2/config v1.31.2 h1:NOaSZpVGEH2Np/c1toSeW0jooNl+9ALmsUTZ8YvkJR0= +github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes= +github.com/aws/aws-sdk-go-v2/credentials v1.18.6 h1:AmmvNEYrru7sYNJnp3pf57lGbiarX4T9qU/6AZ9SucU= +github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 h1:lpdMwTzmuDLkgW7086jE94HweHCqG+uOJwHf3LZs7T0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.4 h1:BE/MNQ86yzTINrfxPPFS86QCBNQeLKY2A0KhDh47+wI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.4/go.mod h1:SPBBhkJxjcrzJBc+qY85e83MQ2q3qdra8fghhkkyrJg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.4 h1:Beh9oVgtQnBgR4sKKzkUBRQpf1GnL4wt0l4s8h2VCJ0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.4/go.mod h1:b17At0o8inygF+c6FOD3rNyYZufPw62o9XJbSfQPgbo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.4 h1:HVSeukL40rHclNcUqVcBwE1YoZhOkoLeBfhUqR3tjIU= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.4/go.mod h1:DnbBOv4FlIXHj2/xmrUQYtawRFC9L9ZmQPz+DBc6X5I= +github.com/aws/aws-sdk-go-v2/service/s3 v1.87.1 h1:2n6Pd67eJwAb/5KCX62/8RTU0aFAAW7V5XIGSghiHrw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.87.1/go.mod h1:w5PC+6GHLkvMJKasYGVloB3TduOtROEMqm15HSuIbw4= +github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0= +github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKSQkyQQ9ftnl/LttQOcGI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo= +github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= +github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/taco/internal/api/routes.go b/taco/internal/api/routes.go index f4495295b..0f3571b7e 100644 --- a/taco/internal/api/routes.go +++ b/taco/internal/api/routes.go @@ -20,11 +20,12 @@ import ( "github.com/diggerhq/digger/opentaco/internal/oidc" "github.com/diggerhq/digger/opentaco/internal/sts" "github.com/diggerhq/digger/opentaco/internal/storage" + "github.com/diggerhq/digger/opentaco/internal/query" "github.com/labstack/echo/v4" ) // RegisterRoutes registers all API routes -func RegisterRoutes(e *echo.Echo, store storage.UnitStore, authEnabled bool) { +func RegisterRoutes(e *echo.Echo, store storage.UnitStore, authEnabled bool, queryStore query.Store, underlyingStore storage.UnitStore, signer *authpkg.Signer) { // Health checks health := observability.NewHealthHandler() e.GET("/healthz", health.Healthz) @@ -51,11 +52,7 @@ func RegisterRoutes(e *echo.Echo, store storage.UnitStore, authEnabled bool) { }) - // Prepare auth deps - signer, err := authpkg.NewSignerFromEnv() - if err != nil { - fmt.Printf("Failed to create JWT signer: %v\n", err) - } + // Prepare auth deps stsi, _ := sts.NewStatelessIssuerFromEnv() ver, _ := oidc.NewFromEnv() @@ -115,44 +112,32 @@ func RegisterRoutes(e *echo.Echo, store storage.UnitStore, authEnabled bool) { // API v1 protected group v1 := e.Group("/v1") var verifyFn middleware.AccessTokenVerifier - if authEnabled { + if authEnabled && signer != nil { verifyFn = func(token string) error { - // JWT only for /v1 - if signer == nil { - return echo.ErrUnauthorized - } _, err := signer.VerifyAccess(token) - if err != nil { - // Debug: log the verification failure - fmt.Printf("[AUTH DEBUG] Token verification failed: %v\n", err) - tokenPreview := token - if len(token) > 50 { - tokenPreview = token[:50] + "..." - } - fmt.Printf("[AUTH DEBUG] Token preview: %s\n", tokenPreview) - } return err } - v1.Use(middleware.RequireAuth(verifyFn)) + v1.Use(middleware.RequireAuth(verifyFn, signer)) } - // Setup RBAC manager if available + // Setup RBAC manager if available (use underlyingStore for type assertion) var rbacManager *rbac.RBACManager - if store != nil { - if s3Store, ok := store.(storage.S3Store); ok { + if underlyingStore != nil { + if s3Store, ok := underlyingStore.(storage.S3Store); ok { rbacStore := rbac.NewS3RBACStore(s3Store.GetS3Client(), s3Store.GetS3Bucket(), s3Store.GetS3Prefix()) rbacManager = rbac.NewRBACManager(rbacStore) } } // Unit handlers (management API) - pass RBAC manager and signer for filtering - unitHandler := unithandlers.NewHandler(store, rbacManager, signer) + unitHandler := unithandlers.NewHandler(store, rbacManager, signer, queryStore) // Management API (units) with RBAC middleware if authEnabled && rbacManager != nil { v1.POST("/units", middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitWrite, "*")(unitHandler.CreateUnit)) // ListUnits does its own RBAC filtering internally, no middleware needed v1.GET("/units", unitHandler.ListUnits) + // v1.GET("/units-fast", unitHandler.ListUnitsFast) v1.GET("/units/:id", middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitRead, "{id}")(unitHandler.GetUnit)) v1.DELETE("/units/:id", middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitDelete, "{id}")(unitHandler.DeleteUnit)) v1.GET("/units/:id/download", middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitRead, "{id}")(unitHandler.DownloadUnit)) @@ -182,20 +167,20 @@ func RegisterRoutes(e *echo.Echo, store storage.UnitStore, authEnabled bool) { } // Terraform HTTP backend proxy with RBAC middleware - backendHandler := backend.NewHandler(store) + backendHandler := backend.NewHandler(store) if authEnabled && rbacManager != nil { v1.GET("/backend/*", middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitRead, "*")(backendHandler.GetState)) v1.POST("/backend/*", middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitWrite, "*")(backendHandler.UpdateState)) v1.PUT("/backend/*", middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitWrite, "*")(backendHandler.UpdateState)) // Explicitly wire non-standard HTTP methods used by Terraform backend - e.Add("LOCK", "/v1/backend/*", middleware.RequireAuth(verifyFn)(middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitLock, "*")(backendHandler.HandleLockUnlock))) - e.Add("UNLOCK", "/v1/backend/*", middleware.RequireAuth(verifyFn)(middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitLock, "*")(backendHandler.HandleLockUnlock))) + e.Add("LOCK", "/v1/backend/*", middleware.RequireAuth(verifyFn, signer)(middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitLock, "*")(backendHandler.HandleLockUnlock))) + e.Add("UNLOCK", "/v1/backend/*", middleware.RequireAuth(verifyFn, signer)(middleware.RBACMiddleware(rbacManager, signer, rbac.ActionUnitLock, "*")(backendHandler.HandleLockUnlock))) } else if authEnabled { v1.GET("/backend/*", backendHandler.GetState) v1.POST("/backend/*", backendHandler.UpdateState) v1.PUT("/backend/*", backendHandler.UpdateState) - e.Add("LOCK", "/v1/backend/*", middleware.RequireAuth(verifyFn)(backendHandler.HandleLockUnlock)) - e.Add("UNLOCK", "/v1/backend/*", middleware.RequireAuth(verifyFn)(backendHandler.HandleLockUnlock)) + e.Add("LOCK", "/v1/backend/*", middleware.RequireAuth(verifyFn, signer)(backendHandler.HandleLockUnlock)) + e.Add("UNLOCK", "/v1/backend/*", middleware.RequireAuth(verifyFn, signer)(backendHandler.HandleLockUnlock)) } else { v1.GET("/backend/*", backendHandler.GetState) v1.POST("/backend/*", backendHandler.UpdateState) @@ -267,7 +252,7 @@ func RegisterRoutes(e *echo.Echo, store storage.UnitStore, authEnabled bool) { } return echo.ErrUnauthorized } - tfeGroup.Use(middleware.RequireAuth(tfeVerify)) + tfeGroup.Use(middleware.RequireAuth(tfeVerify, signer)) } // Move TFE endpoints to protected group diff --git a/taco/internal/middleware/auth.go b/taco/internal/middleware/auth.go index bb958553d..0cce1daff 100644 --- a/taco/internal/middleware/auth.go +++ b/taco/internal/middleware/auth.go @@ -6,15 +6,17 @@ import ( "github.com/diggerhq/digger/opentaco/internal/auth" "github.com/diggerhq/digger/opentaco/internal/rbac" + "github.com/diggerhq/digger/opentaco/internal/storage" "github.com/labstack/echo/v4" + "github.com/diggerhq/digger/opentaco/internal/principal" ) // AccessTokenVerifier is a function that validates an access token. // It should return nil if valid, or an error if invalid. type AccessTokenVerifier func(token string) error -// RequireAuth returns middleware that verifies Bearer access tokens using the provided verifier. -func RequireAuth(verify AccessTokenVerifier) echo.MiddlewareFunc { +// RequireAuth returns middleware that verifies Bearer access tokens and sets principal in context. +func RequireAuth(verify AccessTokenVerifier, signer *auth.Signer) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if verify == nil { @@ -25,9 +27,30 @@ func RequireAuth(verify AccessTokenVerifier) echo.MiddlewareFunc { return c.JSON(http.StatusUnauthorized, map[string]string{"error":"missing_bearer"}) } token := strings.TrimSpace(strings.TrimPrefix(authz, "Bearer ")) - if err := verify(token); err != nil { - return c.JSON(http.StatusUnauthorized, map[string]string{"error":"invalid_token"}) + + // Verify token and get claims in one call + if signer != nil { + claims, err := signer.VerifyAccess(token) + if err != nil { + return c.JSON(http.StatusUnauthorized, map[string]string{"error":"invalid_token"}) + } + + // Set principal in context + p := principal.Principal{ + Subject: claims.Subject, + Email: claims.Email, + Roles: claims.Roles, + Groups: claims.Groups, + } + ctx := storage.ContextWithPrincipal(c.Request().Context(), p) + c.SetRequest(c.Request().WithContext(ctx)) + } else { + // Fallback to generic verify function if no signer + if err := verify(token); err != nil { + return c.JSON(http.StatusUnauthorized, map[string]string{"error":"invalid_token"}) + } } + return next(c) } } diff --git a/taco/internal/principal/principal.go b/taco/internal/principal/principal.go new file mode 100644 index 000000000..d4524fa39 --- /dev/null +++ b/taco/internal/principal/principal.go @@ -0,0 +1,9 @@ +package principal + +// Principal represents the identity of an authenticated user or service. +type Principal struct { + Subject string + Email string + Roles []string + Groups []string +} diff --git a/taco/internal/query/common/sql_store.go b/taco/internal/query/common/sql_store.go new file mode 100644 index 000000000..d700ad611 --- /dev/null +++ b/taco/internal/query/common/sql_store.go @@ -0,0 +1,433 @@ +package common + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/diggerhq/digger/opentaco/internal/query/types" + "github.com/diggerhq/digger/opentaco/internal/rbac" + "gorm.io/gorm" +) + +// SQLStore provides a generic, GORM-based implementation of the Store interface. +// It can be used with any GORM-compatible database dialect (SQLite, Postgres, etc.). +type SQLStore struct { + db *gorm.DB +} + +// NewSQLStore is a constructor for our common store. It takes a pre-configured +// GORM DB object and handles the common setup tasks like migration and view creation. +func NewSQLStore(db *gorm.DB) (*SQLStore, error) { + store := &SQLStore{db: db} + + if err := store.migrate(); err != nil { + return nil, fmt.Errorf("failed to migrate common sql schema: %w", err) + } + if err := store.createViews(); err != nil { + return nil, fmt.Errorf("failed to create common sql views: %w", err) + } + + return store, nil +} + +func (s *SQLStore) migrate() error { + return s.db.AutoMigrate(types.DefaultModels...) +} + +// createViews now introspects the database dialect to use the correct SQL syntax. +func (s *SQLStore) createViews() error { + // Define the body of the view once. + viewBody := ` + WITH user_permissions AS ( + SELECT DISTINCT u.subject as user_subject, r.id as rule_id, r.wildcard_resource, r.effect FROM users u + JOIN user_roles ur ON u.id = ur.user_id JOIN role_permissions rp ON ur.role_id = rp.role_id + JOIN rules r ON rp.permission_id = r.permission_id LEFT JOIN rule_actions ra ON r.id = ra.rule_id + WHERE r.effect = 'allow' AND (r.wildcard_action = true OR ra.action = 'unit.read' OR ra.action IS NULL) + ), + wildcard_access AS ( + SELECT DISTINCT up.user_subject, un.name as unit_name FROM user_permissions up CROSS JOIN units un + WHERE up.wildcard_resource = true + ), + specific_access AS ( + SELECT DISTINCT up.user_subject, un.name as unit_name FROM user_permissions up + JOIN rule_units ru ON up.rule_id = ru.rule_id JOIN units un ON ru.unit_id = un.id + WHERE up.wildcard_resource = false + ) + SELECT user_subject, unit_name FROM wildcard_access + UNION + SELECT user_subject, unit_name FROM specific_access + ` + + var createViewSQL string + dialect := s.db.Dialector.Name() + + // This switch statement is our "carve-out" for different SQL dialects. + switch dialect { + case "sqlserver": + createViewSQL = fmt.Sprintf("CREATE OR ALTER VIEW user_unit_access AS %s", viewBody) + case "sqlite", "postgres": + fallthrough // Use the same syntax for both + default: + // Default to the most common syntax. + createViewSQL = fmt.Sprintf("CREATE OR REPLACE VIEW user_unit_access AS %s", viewBody) + } + + return s.db.Exec(createViewSQL).Error +} + +func (s *SQLStore) Close() error { + sqlDB, err := s.db.DB() + if err != nil { + return err + } + return sqlDB.Close() +} + +func (s *SQLStore) IsEnabled() bool { return true } + +func (s *SQLStore) ListUnits(ctx context.Context, prefix string) ([]types.Unit, error) { + var units []types.Unit + q := s.db.WithContext(ctx).Preload("Tags") + if prefix != "" { + q = q.Where("name LIKE ?", prefix+"%") + } + return units, q.Find(&units).Error +} + +func (s *SQLStore) GetUnit(ctx context.Context, id string) (*types.Unit, error) { + var unit types.Unit + err := s.db.WithContext(ctx).Preload("Tags").Where("name = ?", id).First(&unit).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, types.ErrNotFound + } + return nil, err + } + return &unit, nil +} + +func (s *SQLStore) SyncEnsureUnit(ctx context.Context, unitName string) error { + unit := types.Unit{Name: unitName} + return s.db.WithContext(ctx).FirstOrCreate(&unit, types.Unit{Name: unitName}).Error +} + + +func (s *SQLStore) SyncUnitMetadata(ctx context.Context, unitName string, size int64, updated time.Time) error { + return s.db.WithContext(ctx).Model(&types.Unit{}). + Where("name = ?", unitName). + Updates(map[string]interface{}{ + "size": size, + "updated_at": updated, + }).Error +} + +func (s *SQLStore) SyncDeleteUnit(ctx context.Context, unitName string) error { + return s.db.WithContext(ctx).Where("name = ?", unitName).Delete(&types.Unit{}).Error +} + +func (s *SQLStore) SyncUnitLock(ctx context.Context, unitName string, lockID, lockWho string, lockCreated time.Time) error { + return s.db.WithContext(ctx).Model(&types.Unit{}). + Where("name = ?", unitName). + Updates(map[string]interface{}{ + "locked": true, + "lock_id": lockID, + "lock_who": lockWho, + "lock_created": lockCreated, + }).Error +} + +func (s *SQLStore) SyncUnitUnlock(ctx context.Context, unitName string) error { + return s.db.WithContext(ctx).Model(&types.Unit{}). + Where("name = ?", unitName). + Updates(map[string]interface{}{ + "locked": false, + "lock_id": "", + "lock_who": "", + "lock_created": time.Time{}, + }).Error +} + +func (s *SQLStore) ListUnitsForUser(ctx context.Context, userSubject string, prefix string) ([]types.Unit, error) { + var units []types.Unit + q := s.db.WithContext(ctx).Table("units").Select("units.*"). + Joins("JOIN user_unit_access ON units.name = user_unit_access.unit_name"). + Where("user_unit_access.user_subject = ?", userSubject). + Preload("Tags") + + if prefix != "" { + q = q.Where("units.name LIKE ?", prefix+"%") + } + + return units, q.Find(&units).Error +} + +func (s *SQLStore) FilterUnitIDsByUser(ctx context.Context, userSubject string, unitIDs []string) ([]string, error) { + if len(unitIDs) == 0 { + return []string{}, nil + } + var allowedUnitIDs []string + return allowedUnitIDs, s.db.WithContext(ctx).Table("user_unit_access"). + Select("unit_name"). + Where("user_subject = ?", userSubject). + Where("unit_name IN ?", unitIDs). + Pluck("unit_name", &allowedUnitIDs).Error +} + +func (s *SQLStore) CanPerformAction(ctx context.Context, userSubject string, action string, resourceID string) (bool, error) { + var allowed int + // GORM's Raw SQL uses '?' and the dialect converts it to '$1', etc. for Postgres automatically. + querySQL := ` + SELECT MAX(CASE WHEN r.effect = 'allow' THEN 1 ELSE 0 END) FROM users u + JOIN user_roles ur ON u.id = ur.user_id JOIN role_permissions rp ON ur.role_id = rp.role_id + JOIN rules r ON rp.permission_id = r.permission_id + WHERE u.subject = ? AND (r.wildcard_action = true OR EXISTS (SELECT 1 FROM rule_actions ra WHERE ra.rule_id = r.id AND ra.action = ?)) + AND (r.wildcard_resource = true OR EXISTS (SELECT 1 FROM rule_units ru JOIN units un ON ru.unit_id = un.id WHERE ru.rule_id = r.id AND un.name = ?)) + ` + err := s.db.WithContext(ctx).Raw(querySQL, userSubject, action, resourceID).Scan(&allowed).Error + return allowed == 1, err +} + +func (s *SQLStore) HasRBACRoles(ctx context.Context) (bool, error) { + var count int64 + // We don't need to count them all, we just need to know if at least one exists. + if err := s.db.WithContext(ctx).Model(&types.Role{}).Limit(1).Count(&count).Error; err != nil { + return false, err + } + return count > 0, nil +} + +// SyncPermission syncs a permission from storage to the database +func (s *SQLStore) SyncPermission(ctx context.Context, permissionData interface{}) error { + // Import at the top: "github.com/diggerhq/digger/opentaco/internal/rbac" + perm, ok := permissionData.(*rbac.Permission) + if !ok { + return fmt.Errorf("invalid permission data type") + } + + return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + // 1) Upsert the permission + p := types.Permission{ + PermissionId: perm.ID, + Name: perm.Name, + Description: perm.Description, + CreatedBy: perm.CreatedBy, + CreatedAt: perm.CreatedAt, + } + + // Upsert using FirstOrCreate + if err := tx.Where(types.Permission{PermissionId: perm.ID}). + Assign(p). + FirstOrCreate(&p).Error; err != nil { + return fmt.Errorf("upsert permission %s: %w", perm.ID, err) + } + + // 2) Clear old rules for idempotency + if err := tx.Where("permission_id = ?", p.ID).Delete(&types.Rule{}).Error; err != nil { + return fmt.Errorf("clear rules for %s: %w", perm.ID, err) + } + + // 3) Insert new rules + for _, ruleData := range perm.Rules { + rule := types.Rule{ + PermissionID: p.ID, + Effect: strings.ToLower(ruleData.Effect), + WildcardAction: hasStarAction(ruleData.Actions), + WildcardResource: hasStarResource(ruleData.Resources), + } + + if err := tx.Create(&rule).Error; err != nil { + return fmt.Errorf("create rule: %w", err) + } + + // Create rule actions if not wildcard + if !rule.WildcardAction { + for _, action := range ruleData.Actions { + ra := types.RuleAction{ + RuleID: rule.ID, + Action: string(action), + } + if err := tx.Create(&ra).Error; err != nil { + return fmt.Errorf("create rule action: %w", err) + } + } + } + + // Create rule units if not wildcard + if !rule.WildcardResource { + for _, resourceName := range ruleData.Resources { + // Ensure unit exists + var unit types.Unit + if err := tx.Where("name = ?", resourceName). + Attrs(types.Unit{Name: resourceName}). + FirstOrCreate(&unit).Error; err != nil { + return fmt.Errorf("ensure unit %q: %w", resourceName, err) + } + + ru := types.RuleUnit{ + RuleID: rule.ID, + UnitID: unit.ID, + } + if err := tx.Create(&ru).Error; err != nil { + return fmt.Errorf("create rule unit: %w", err) + } + } + } + } + + return nil + }) +} + +// SyncRole syncs a role from storage to the database +func (s *SQLStore) SyncRole(ctx context.Context, roleData interface{}) error { + role, ok := roleData.(*rbac.Role) + if !ok { + return fmt.Errorf("invalid role data type") + } + + return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + // 1) Upsert role + r := types.Role{ + RoleId: role.ID, + Name: role.Name, + Description: role.Description, + CreatedBy: role.CreatedBy, + CreatedAt: role.CreatedAt, + } + + if err := tx.Where(types.Role{RoleId: role.ID}). + Assign(r). + FirstOrCreate(&r).Error; err != nil { + return fmt.Errorf("upsert role %q: %w", role.ID, err) + } + + // 2) Find all referenced permissions + perms := make([]types.Permission, 0, len(role.Permissions)) + if len(role.Permissions) > 0 { + var existing []types.Permission + if err := tx.Where("permission_id IN ?", role.Permissions).Find(&existing).Error; err != nil { + return fmt.Errorf("lookup permissions for role %q: %w", role.ID, err) + } + + exists := make(map[string]types.Permission) + for _, p := range existing { + exists[p.PermissionId] = p + } + + // Create missing permissions as placeholders + for _, pid := range role.Permissions { + if p, ok := exists[pid]; ok { + perms = append(perms, p) + } else { + np := types.Permission{ + PermissionId: pid, + Name: pid, + Description: "", + CreatedBy: role.CreatedBy, + } + if err := tx.Where(types.Permission{PermissionId: pid}). + Attrs(np). + FirstOrCreate(&np).Error; err != nil { + return fmt.Errorf("create missing permission %q: %w", pid, err) + } + perms = append(perms, np) + } + } + } + + // 3) Replace role->permission associations + if err := tx.Model(&r).Association("Permissions").Replace(perms); err != nil { + return fmt.Errorf("set role permissions for %q: %w", role.ID, err) + } + + return nil + }) +} + +// SyncUser syncs a user assignment from storage to the database +func (s *SQLStore) SyncUser(ctx context.Context, userData interface{}) error { + user, ok := userData.(*rbac.UserAssignment) + if !ok { + return fmt.Errorf("invalid user data type") + } + + return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + // 1) Upsert user + u := types.User{ + Subject: user.Subject, + Email: user.Email, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + Version: user.Version, + } + + if err := tx.Where(types.User{Subject: user.Subject}). + Assign(u). + FirstOrCreate(&u).Error; err != nil { + return fmt.Errorf("upsert user %q: %w", user.Subject, err) + } + + // 2) Find all referenced roles + roles := make([]types.Role, 0, len(user.Roles)) + if len(user.Roles) > 0 { + var existing []types.Role + if err := tx.Where("role_id IN ?", user.Roles).Find(&existing).Error; err != nil { + return fmt.Errorf("lookup roles: %w", err) + } + + byID := make(map[string]types.Role) + for _, r := range existing { + byID[r.RoleId] = r + } + + // Create missing roles as placeholders + for _, rid := range user.Roles { + if r, ok := byID[rid]; ok { + roles = append(roles, r) + } else { + nr := types.Role{ + RoleId: rid, + Name: rid, + Description: "", + CreatedBy: user.Subject, + } + if err := tx.Where(types.Role{RoleId: rid}). + Attrs(nr). + FirstOrCreate(&nr).Error; err != nil { + return fmt.Errorf("create missing role %q: %w", rid, err) + } + roles = append(roles, nr) + } + } + } + + // 3) Replace user->role associations + if err := tx.Model(&u).Association("Roles").Replace(roles); err != nil { + return fmt.Errorf("set user roles for %q: %w", user.Subject, err) + } + + return nil + }) +} + +// Helper functions for checking wildcards +func hasStarAction(actions []rbac.Action) bool { + for _, a := range actions { + if string(a) == "*" { + return true + } + } + return false +} + +func hasStarResource(resources []string) bool { + for _, r := range resources { + if r == "*" { + return true + } + } + return false +} diff --git a/taco/internal/query/config.go b/taco/internal/query/config.go new file mode 100644 index 000000000..19f2348c1 --- /dev/null +++ b/taco/internal/query/config.go @@ -0,0 +1,50 @@ +package query + +import "time" + + +type Config struct { + Backend string `envconfig:"QUERY_BACKEND" default:"sqlite"` + SQLite SQLiteConfig `envconfig:"SQLITE"` + Postgres PostgresConfig `envconfig:"POSTGRES"` + MSSQL MSSQLConfig `envconfig:"MSSQL"` + MySQL MySQLConfig `envconfig:"MYSQL"` +} + + +type SQLiteConfig struct { + Path string `envconfig:"PATH" default:"./data/taco.db"` + Cache string `envconfig:"CACHE" default:"shared"` + BusyTimeout time.Duration `envconfig:"BUSY_TIMEOUT" default:"5s"` + MaxOpenConns int `envconfig:"MAX_OPEN_CONNS" default:"1"` + MaxIdleConns int `envconfig:"MAX_IDLE_CONNS" default:"1"` + PragmaJournalMode string `envconfig:"PRAGMA_JOURNAL_MODE" default:"WAL"` + PragmaForeignKeys string `envconfig:"PRAGMA_FOREIGN_KEYS" default:"ON"` + PragmaBusyTimeout string `envconfig:"PRAGMA_BUSY_TIMEOUT" default:"5000"` +} + +type PostgresConfig struct { + Host string `envconfig:"HOST" default:"localhost"` + Port int `envconfig:"PORT" default:"5432"` + User string `envconfig:"USER" default:"postgres"` + Password string `envconfig:"PASSWORD"` + DBName string `envconfig:"DBNAME" default:"taco"` + SSLMode string `envconfig:"SSLMODE" default:"disable"` +} + +type MSSQLConfig struct { + Host string `envconfig:"HOST" default:"localhost"` + Port int `envconfig:"PORT" default:"1433"` + User string `envconfig:"USER"` + Password string `envconfig:"PASSWORD"` + DBName string `envconfig:"DBNAME" default:"taco"` +} + +type MySQLConfig struct { + Host string `envconfig:"HOST" default:"localhost"` + Port int `envconfig:"PORT" default:"3306"` + User string `envconfig:"USER" default:"root"` + Password string `envconfig:"PASSWORD"` + DBName string `envconfig:"DBNAME" default:"taco"` + Charset string `envconfig:"CHARSET" default:"utf8mb4"` +} \ No newline at end of file diff --git a/taco/internal/query/interface.go b/taco/internal/query/interface.go new file mode 100644 index 000000000..adf407100 --- /dev/null +++ b/taco/internal/query/interface.go @@ -0,0 +1,41 @@ +package query + +import ( + "context" + "github.com/diggerhq/digger/opentaco/internal/query/types" + "time" +) + +type QueryStore interface { + Close() error + IsEnabled() bool +} + +type UnitQuery interface { + ListUnits(ctx context.Context, prefix string) ([]types.Unit, error) + GetUnit(ctx context.Context, id string) (*types.Unit, error) + SyncEnsureUnit(ctx context.Context, unitName string) error + SyncUnitMetadata(ctx context.Context, unitName string, size int64, updated time.Time) error + SyncUnitLock(ctx context.Context, unitName string, lockID, lockWho string, lockCreated time.Time) error + SyncUnitUnlock(ctx context.Context, unitName string) error + SyncDeleteUnit(ctx context.Context, unitName string) error +} + +type RBACQuery interface { + FilterUnitIDsByUser(ctx context.Context, userSubject string, unitIDs []string) ([]string, error) + ListUnitsForUser(ctx context.Context, userSubject string, prefix string) ([]types.Unit, error) + CanPerformAction(ctx context.Context, userSubject string, action string, resourceID string) (bool, error) + HasRBACRoles(ctx context.Context) (bool, error) + + SyncPermission(ctx context.Context, permission interface{}) error + SyncRole(ctx context.Context, role interface{}) error + SyncUser(ctx context.Context, user interface{}) error +} + +type Store interface { + QueryStore + UnitQuery + RBACQuery +} + + diff --git a/taco/internal/query/mssql/store.go b/taco/internal/query/mssql/store.go new file mode 100644 index 000000000..348514cc4 --- /dev/null +++ b/taco/internal/query/mssql/store.go @@ -0,0 +1,29 @@ +package mssql + +import ( + "fmt" + + "github.com/diggerhq/digger/opentaco/internal/query" + "github.com/diggerhq/digger/opentaco/internal/query/common" + "gorm.io/driver/sqlserver" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// NewMSSQLStore creates a new MS SQL-backed query store. +// Its only job is to establish the DB connection and pass it to the common SQLStore. +func NewMSSQLStore(cfg query.MSSQLConfig) (query.Store, error) { + // DSN format: sqlserver://username:password@host:port?database=dbname + dsn := fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", + cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName) + + db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), // Or Silent + }) + if err != nil { + return nil, fmt.Errorf("failed to connect to mssql: %w", err) + } + + // Hand off to the common, dialect-aware SQLStore engine. + return common.NewSQLStore(db) +} diff --git a/taco/internal/query/mysql/store.go b/taco/internal/query/mysql/store.go new file mode 100644 index 000000000..97c83b16e --- /dev/null +++ b/taco/internal/query/mysql/store.go @@ -0,0 +1,29 @@ +package mysql + +import ( + "fmt" + + "github.com/diggerhq/digger/opentaco/internal/query" + "github.com/diggerhq/digger/opentaco/internal/query/common" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// NewMySQLStore creates a new MySQL-backed query store. +// Its only job is to establish the DB connection and pass it to the common SQLStore. +func NewMySQLStore(cfg query.MySQLConfig) (query.Store, error) { + // DSN format: user:pass@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local", + cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName, cfg.Charset) + + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), // Or Silent + }) + if err != nil { + return nil, fmt.Errorf("failed to connect to mysql: %w", err) + } + + // Hand off to the common, dialect-aware SQLStore engine. + return common.NewSQLStore(db) +} diff --git a/taco/internal/query/postgres/store.go b/taco/internal/query/postgres/store.go new file mode 100644 index 000000000..bf7fe9ba2 --- /dev/null +++ b/taco/internal/query/postgres/store.go @@ -0,0 +1,28 @@ +package postgres + +import ( + "fmt" + + "github.com/diggerhq/digger/opentaco/internal/query" + "github.com/diggerhq/digger/opentaco/internal/query/common" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// NewPostgresStore creates a new PostgreSQL-backed query store. +// Its only job is to establish the DB connection and pass it to the common SQLStore. +func NewPostgresStore(cfg query.PostgresConfig) (query.Store, error) { + dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s", + cfg.Host, cfg.User, cfg.Password, cfg.DBName, cfg.Port, cfg.SSLMode) + + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), // Or Silent + }) + if err != nil { + return nil, fmt.Errorf("failed to connect to postgres: %w", err) + } + + // Call the constructor from the 'common' package, breaking the cycle. + return common.NewSQLStore(db) +} \ No newline at end of file diff --git a/taco/internal/query/sqlite/store.go b/taco/internal/query/sqlite/store.go new file mode 100644 index 000000000..75e5ed61a --- /dev/null +++ b/taco/internal/query/sqlite/store.go @@ -0,0 +1,44 @@ +package sqlite + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/diggerhq/digger/opentaco/internal/query" + "github.com/diggerhq/digger/opentaco/internal/query/common" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// NewSQLiteQueryStore creates a new SQLite-backed query store. +func NewSQLiteQueryStore(cfg query.SQLiteConfig) (query.Store, error) { + if err := os.MkdirAll(filepath.Dir(cfg.Path), 0755); err != nil { + return nil, fmt.Errorf("create db dir: %v", err) + } + + dsn := fmt.Sprintf("file:%s?cache=%s", cfg.Path, cfg.Cache) + db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), // Or Silent + }) + if err != nil { + return nil, fmt.Errorf("open sqlite: %v", err) + } + + // Apply SQLite-specific PRAGMAs + if err := db.Exec(fmt.Sprintf("PRAGMA journal_mode = %s;", strings.ToUpper(cfg.PragmaJournalMode))).Error; err != nil { + return nil, fmt.Errorf("apply journal_mode: %w", err) + } + if err := db.Exec(fmt.Sprintf("PRAGMA foreign_keys = %s;", strings.ToUpper(cfg.PragmaForeignKeys))).Error; err != nil { + return nil, fmt.Errorf("apply foreign_keys: %w", err) + } + if err := db.Exec(fmt.Sprintf("PRAGMA busy_timeout = %s;", cfg.PragmaBusyTimeout)).Error; err != nil { + return nil, fmt.Errorf("apply busy_timeout: %w", err) + } + + // Create the common SQLStore with our configured DB object, breaking the cycle. + return common.NewSQLStore(db) +} + diff --git a/taco/internal/query/types/errors.go b/taco/internal/query/types/errors.go new file mode 100644 index 000000000..f45a6f507 --- /dev/null +++ b/taco/internal/query/types/errors.go @@ -0,0 +1,12 @@ +package types + +import ( + "errors" +) + + + +var ( + ErrNotSupported = errors.New("Query operation not supported by this backend") + ErrNotFound = errors.New("Not found") +) diff --git a/taco/internal/query/types/models.go b/taco/internal/query/types/models.go new file mode 100644 index 000000000..8b331f51d --- /dev/null +++ b/taco/internal/query/types/models.go @@ -0,0 +1,144 @@ +package types + + +import ( + "time" +) + + +type Role struct { + ID int64 `gorm:"primaryKey"` + RoleId string `gorm:"not null;uniqueIndex"`// like "admin" + Name string //" admin role" + Description string // "Admin Role with full access" + Permissions []Permission `gorm:"many2many:role_permissions;constraint:OnDelete:CASCADE,OnUpdate:CASCADE"` + CreatedAt time.Time//timestamp + CreatedBy string //subject of creator (self for admin) +} + + + + +type Permission struct { + ID int64 `gorm:"primaryKey"` + PermissionId string `gorm:"not null;uniqueIndex"` + Name string // "admin permission" + Description string // "Admin permission allowing all action" + Rules []Rule `gorm:"constraint:OnDelete:CASCADE"` // [{"actions":["unit.read","unit.write","unit.lock","unit.delete","rbac.manage"],"resources":["*"],"effect":"allow"}] FK + CreatedBy string // subject of creator (self for admin) + CreatedAt time.Time +} + +type Rule struct { + ID int64 `gorm:"primaryKey"` + PermissionID int64 `gorm:"index;not null"` + Effect string `gorm:"size:8;not null;default:allow"` // "allow" | "deny" + WildcardAction bool `gorm:"not null;default:false"` + WildcardResource bool `gorm:"not null;default:false"` + Actions []RuleAction `gorm:"constraint:OnDelete:CASCADE"` + UnitTargets []RuleUnit `gorm:"constraint:OnDelete:CASCADE"` + TagTargets []RuleUnitTag `gorm:"constraint:OnDelete:CASCADE"` +} + + + +type RuleAction struct { + ID int64 `gorm:"primaryKey"` + RuleID int64 `gorm:"index;not null"` + Action string `gorm:"size:128;not null;index"` + // UNIQUE (rule_id, action) +} +func (RuleAction) TableName() string { return "rule_actions" } + +type RuleUnit struct { + ID int64 `gorm:"primaryKey"` + RuleID int64 `gorm:"index;not null"` + UnitID int64 `gorm:"index;not null"` + // UNIQUE (rule_id, resource_id) +} +func (RuleUnit) TableName() string { return "rule_units" } + +type RuleUnitTag struct { + ID int64 `gorm:"primaryKey"` + RuleID int64 `gorm:"index;not null"` + TagID int64 `gorm:"index;not null"` + // UNIQUE (rule_id, tag_id) +} +func (RuleUnitTag) TableName() string { return "rule_unit_tags" } + + + + +type User struct { + ID int64 `gorm:"primaryKey"` + Subject string `gorm:"not null;uniqueIndex"` + Email string `gorm:"not null;uniqueIndex"` + Roles []Role `gorm:"many2many:user_roles;constraint:OnDelete:CASCADE,OnUpdate:CASCADE"` + CreatedAt time.Time + UpdatedAt time.Time + Version int64 //"1" +} + +type Unit struct { + ID int64 `gorm:"primaryKey"` + Name string `gorm:"uniqueIndex"` + Size int64 `gorm:"default:0"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + Locked bool `gorm:"default:false"` + LockID string `gorm:"default:''"` + LockWho string `gorm:"default:''"` + LockCreated time.Time + Tags []Tag `gorm:"many2many:unit_tags;constraint:OnDelete:CASCADE,OnUpdate:CASCADE"` +} + +type Tag struct { + ID int64 `gorm:"primaryKey"` + Name string `gorm:"uniqueIndex"` + +} + + +//explicit joins + + +type UnitTag struct { + UnitID int64 `gorm:"primaryKey;index"` + TagID int64 `gorm:"primaryKey;index"` +} +func (UnitTag) TableName() string { return "unit_tags" } + + +type UserRole struct { + UserID int64 `gorm:"primaryKey;index"` + RoleID int64 `gorm:"primaryKey;index"` +} +func (UserRole) TableName() string { return "user_roles" } + + + +type RolePermission struct { + RoleID int64 `gorm:"primaryKey;index"` + PermissionID int64 `gorm:"primaryKey;index"` +} + + +func (RolePermission) TableName() string { return "role_permissions" } + + + + +// set the models that will be populated on startup for each DB type; add any new tables here: +var DefaultModels = []any{ + &User{}, + &Role{}, + &UserRole{}, + &Permission{}, + &Rule{}, + &RuleAction{}, + &RuleUnit{}, + &RuleUnitTag{}, + &RolePermission{}, + &Unit{}, + &Tag{}, + &UnitTag{}, +} \ No newline at end of file diff --git a/taco/internal/queryfactory/factory.go b/taco/internal/queryfactory/factory.go new file mode 100644 index 000000000..b22fe2db4 --- /dev/null +++ b/taco/internal/queryfactory/factory.go @@ -0,0 +1,33 @@ +// Package queryfactory is responsible for constructing a query.Store. +// It is in a separate package from query to prevent circular dependencies, +// as it needs to import the various database-specific store packages. +package queryfactory + +import ( + "fmt" + "strings" + + "github.com/diggerhq/digger/opentaco/internal/query" + "github.com/diggerhq/digger/opentaco/internal/query/mssql" + "github.com/diggerhq/digger/opentaco/internal/query/mysql" + "github.com/diggerhq/digger/opentaco/internal/query/postgres" + "github.com/diggerhq/digger/opentaco/internal/query/sqlite" +) + +// NewQueryStore creates a new query.Store based on the provided configuration. +func NewQueryStore(cfg query.Config) (query.Store, error) { + backend := strings.ToLower(cfg.Backend) + + switch backend { + case "sqlite", "": + return sqlite.NewSQLiteQueryStore(cfg.SQLite) + case "postgres": + return postgres.NewPostgresStore(cfg.Postgres) + case "mssql": + return mssql.NewMSSQLStore(cfg.MSSQL) + case "mysql": + return mysql.NewMySQLStore(cfg.MySQL) + default: + return nil, fmt.Errorf("unsupported TACO_QUERY_BACKEND value: %q (supported: sqlite, postgres, mssql, mysql)", backend) + } +} diff --git a/taco/internal/rbac/s3store.go b/taco/internal/rbac/s3store.go index e3bf4b159..5ed87b2a3 100644 --- a/taco/internal/rbac/s3store.go +++ b/taco/internal/rbac/s3store.go @@ -15,15 +15,17 @@ import ( "github.com/aws/smithy-go" ) -// s3RBACStore implements RBACStore backed by S3 +// s3RBACStore implements storage.RBACStore backed by S3 type s3RBACStore struct { client *s3.Client bucket string prefix string } -// NewS3RBACStore creates a new S3-backed RBAC store -func NewS3RBACStore(client *s3.Client, bucket, prefix string) RBACStore { +// NewS3RBACStore creates a new S3-backed RBAC store. +// Returns the concrete type which implements both storage.RBACStore (read-only) +// and rbac.RBACStore (full CRUD operations). +func NewS3RBACStore(client *s3.Client, bucket, prefix string) *s3RBACStore { return &s3RBACStore{ client: client, bucket: bucket, @@ -165,45 +167,11 @@ func (s *s3RBACStore) GetPermission(ctx context.Context, id string) (*Permission } func (s *s3RBACStore) ListPermissions(ctx context.Context) ([]*Permission, error) { - permissionsPrefix := s.key("rbac", "permissions") + "/" - - var permissions []*Permission - var token *string - - for { - resp, err := s.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ - Bucket: &s.bucket, - Prefix: aws.String(permissionsPrefix), - ContinuationToken: token, - }) - if err != nil { - return nil, err - } - - for _, obj := range resp.Contents { - key := aws.ToString(obj.Key) - if !strings.HasSuffix(key, ".json") { - continue - } - - // Extract permission ID from key - permissionID := strings.TrimSuffix(strings.TrimPrefix(key, permissionsPrefix), ".json") - - permission, err := s.GetPermission(ctx, permissionID) - if err != nil { - continue // Skip invalid permissions - } - permissions = append(permissions, permission) - } - - if aws.ToBool(resp.IsTruncated) && resp.NextContinuationToken != nil { - token = resp.NextContinuationToken - continue - } - break + perms, err := s.listPermissionsTyped(ctx) + if err != nil { + return nil, err } - - return permissions, nil + return perms, nil } func (s *s3RBACStore) DeletePermission(ctx context.Context, id string) error { @@ -287,44 +255,10 @@ func (s *s3RBACStore) GetRole(ctx context.Context, id string) (*Role, error) { } func (s *s3RBACStore) ListRoles(ctx context.Context) ([]*Role, error) { - rolesPrefix := s.key("rbac", "roles") + "/" - - var roles []*Role - var token *string - - for { - resp, err := s.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ - Bucket: &s.bucket, - Prefix: aws.String(rolesPrefix), - ContinuationToken: token, - }) - if err != nil { - return nil, err - } - - for _, obj := range resp.Contents { - key := aws.ToString(obj.Key) - if !strings.HasSuffix(key, ".json") { - continue - } - - // Extract role ID from key - roleID := strings.TrimSuffix(strings.TrimPrefix(key, rolesPrefix), ".json") - - role, err := s.GetRole(ctx, roleID) - if err != nil { - continue // Skip invalid roles - } - roles = append(roles, role) - } - - if aws.ToBool(resp.IsTruncated) && resp.NextContinuationToken != nil { - token = resp.NextContinuationToken - continue - } - break + roles, err := s.listRolesTyped(ctx) + if err != nil { + return nil, err } - return roles, nil } @@ -487,8 +421,8 @@ func (s *s3RBACStore) GetUserAssignment(ctx context.Context, subject string) (*U // GetUserAssignmentByEmail finds a user assignment by email address func (s *s3RBACStore) GetUserAssignmentByEmail(ctx context.Context, email string) (*UserAssignment, error) { - // List all user assignments and find by email - assignments, err := s.ListUserAssignments(ctx) + // Use the typed internal method, not the interface{} wrapper + assignments, err := s.listUserAssignmentsTyped(ctx) if err != nil { return nil, err } @@ -503,48 +437,11 @@ func (s *s3RBACStore) GetUserAssignmentByEmail(ctx context.Context, email string } func (s *s3RBACStore) ListUserAssignments(ctx context.Context) ([]*UserAssignment, error) { - usersPrefix := s.key("rbac", "users") + "/" - - var assignments []*UserAssignment - var token *string - - for { - resp, err := s.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ - Bucket: &s.bucket, - Prefix: aws.String(usersPrefix), - ContinuationToken: token, - }) - if err != nil { - return nil, err - } - - for _, obj := range resp.Contents { - key := aws.ToString(obj.Key) - if !strings.HasSuffix(key, ".json") { - continue - } - - // Extract subject from key - subject := strings.TrimSuffix(strings.TrimPrefix(key, usersPrefix), ".json") - // Convert back from safe format - subject = strings.ReplaceAll(subject, "_", "/") - subject = strings.ReplaceAll(subject, "_", ":") - - assignment, err := s.GetUserAssignment(ctx, subject) - if err != nil { - continue // Skip invalid assignments - } - assignments = append(assignments, assignment) - } - - if aws.ToBool(resp.IsTruncated) && resp.NextContinuationToken != nil { - token = resp.NextContinuationToken - continue - } - break + users, err := s.listUserAssignmentsTyped(ctx) + if err != nil { + return nil, err } - - return assignments, nil + return users, nil } func (s *s3RBACStore) saveUserAssignment(ctx context.Context, assignment *UserAssignment) error { @@ -590,3 +487,135 @@ func (s *s3RBACStore) saveUserAssignmentWithVersion(ctx context.Context, assignm return err } + +// listPermissionsTyped is the internal typed implementation +func (s *s3RBACStore) listPermissionsTyped(ctx context.Context) ([]*Permission, error) { + permissionsPrefix := s.key("rbac", "permissions") + "/" + + var permissions []*Permission + var token *string + + for { + resp, err := s.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: &s.bucket, + Prefix: aws.String(permissionsPrefix), + ContinuationToken: token, + }) + if err != nil { + return nil, err + } + + for _, obj := range resp.Contents { + key := aws.ToString(obj.Key) + if !strings.HasSuffix(key, ".json") { + continue + } + + // Extract permission ID from key + permissionID := strings.TrimSuffix(strings.TrimPrefix(key, permissionsPrefix), ".json") + + permission, err := s.GetPermission(ctx, permissionID) + if err != nil { + continue // Skip invalid permissions + } + permissions = append(permissions, permission) + } + + if aws.ToBool(resp.IsTruncated) && resp.NextContinuationToken != nil { + token = resp.NextContinuationToken + continue + } + break + } + + return permissions, nil +} + +// listRolesTyped is the internal typed implementation +func (s *s3RBACStore) listRolesTyped(ctx context.Context) ([]*Role, error) { + rolesPrefix := s.key("rbac", "roles") + "/" + + var roles []*Role + var token *string + + for { + resp, err := s.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: &s.bucket, + Prefix: aws.String(rolesPrefix), + ContinuationToken: token, + }) + if err != nil { + return nil, err + } + + for _, obj := range resp.Contents { + key := aws.ToString(obj.Key) + if !strings.HasSuffix(key, ".json") { + continue + } + + // Extract role ID from key + roleID := strings.TrimSuffix(strings.TrimPrefix(key, rolesPrefix), ".json") + + role, err := s.GetRole(ctx, roleID) + if err != nil { + continue // Skip invalid roles + } + roles = append(roles, role) + } + + if aws.ToBool(resp.IsTruncated) && resp.NextContinuationToken != nil { + token = resp.NextContinuationToken + continue + } + break + } + + return roles, nil +} + +// listUserAssignmentsTyped is the internal typed implementation +func (s *s3RBACStore) listUserAssignmentsTyped(ctx context.Context) ([]*UserAssignment, error) { + usersPrefix := s.key("rbac", "users") + "/" + + var assignments []*UserAssignment + var token *string + + for { + resp, err := s.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: &s.bucket, + Prefix: aws.String(usersPrefix), + ContinuationToken: token, + }) + if err != nil { + return nil, err + } + + for _, obj := range resp.Contents { + key := aws.ToString(obj.Key) + if !strings.HasSuffix(key, ".json") { + continue + } + + // Extract subject from key + subject := strings.TrimSuffix(strings.TrimPrefix(key, usersPrefix), ".json") + // Convert back from safe format + subject = strings.ReplaceAll(subject, "_", "/") + subject = strings.ReplaceAll(subject, "_", ":") + + assignment, err := s.GetUserAssignment(ctx, subject) + if err != nil { + continue // Skip invalid assignments + } + assignments = append(assignments, assignment) + } + + if aws.ToBool(resp.IsTruncated) && resp.NextContinuationToken != nil { + token = resp.NextContinuationToken + continue + } + break + } + + return assignments, nil +} diff --git a/taco/internal/storage/authorizer.go b/taco/internal/storage/authorizer.go new file mode 100644 index 000000000..1efeb7d2b --- /dev/null +++ b/taco/internal/storage/authorizer.go @@ -0,0 +1,198 @@ +package storage + +import ( + "context" + "errors" + "log" + "time" + + "github.com/diggerhq/digger/opentaco/internal/principal" + "github.com/diggerhq/digger/opentaco/internal/query" +) + +// principalKey is a private type to prevent key collisions in context. +type principalKey string + +const userPrincipalKey principalKey = "user" + +// ContextWithPrincipal returns a new context with the given user principal. +func ContextWithPrincipal(ctx context.Context, p principal.Principal) context.Context { + return context.WithValue(ctx, userPrincipalKey, p) +} + +// principalFromContext retrieves the user principal from the context. +func principalFromContext(ctx context.Context) (principal.Principal, error) { + p := ctx.Value(userPrincipalKey) + if p == nil { + return principal.Principal{}, errors.New("no user principal in context") + } + pr, ok := p.(principal.Principal) + if !ok { + return principal.Principal{}, errors.New("invalid user principal type in context") + } + return pr, nil +} + +// AuthorizingStore is a decorator that enforces role-based access control +// on an underlying UnitStore. +type AuthorizingStore struct { + nextStore UnitStore // The next store in the chain (e.g., OrchestratingStore) + queryStore query.Store // Needed to perform the RBAC checks +} + +// NewAuthorizingStore creates a new store that wraps another with an authorization layer. +func NewAuthorizingStore(next UnitStore, qs query.Store) UnitStore { + return &AuthorizingStore{ + nextStore: next, + queryStore: qs, + } +} + +// List intercepts the call and returns only the units the user is permitted to see. +func (s *AuthorizingStore) List(ctx context.Context, prefix string) ([]*UnitMetadata, error) { + principal, err := principalFromContext(ctx) + if err != nil { + return nil, errors.New("unauthorized") + } + + // Use the optimized query that fetches ONLY the units the user is allowed to see. + units, err := s.queryStore.ListUnitsForUser(ctx, principal.Subject, prefix) + if err != nil { + return nil, err + } + + metadata := make([]*UnitMetadata, len(units)) + for i, u := range units { + var lockInfo *LockInfo + if u.Locked { + lockInfo = &LockInfo{ + ID: u.LockID, + Who: u.LockWho, + Created: u.LockCreated, + } + } + metadata[i] = &UnitMetadata{ + ID: u.Name, + Size: u.Size, + Updated: u.UpdatedAt, + Locked: u.Locked, + LockInfo: lockInfo, + } + } + + return metadata, nil +} + +// checkPermission is a new helper to centralize permission checks. +func (s *AuthorizingStore) checkPermission(ctx context.Context, action, unitID string) error { + principal, err := principalFromContext(ctx) + if err != nil { + return errors.New("unauthorized") + } + + allowed, err := s.queryStore.CanPerformAction(ctx, principal.Subject, action, unitID) + if err != nil { + log.Printf("RBAC check failed for user '%s', action '%s' on unit '%s': %v", principal.Subject, action, unitID, err) + return errors.New("internal authorization error") + } + if !allowed { + return errors.New("forbidden") + } + return nil +} + +// Get checks for 'unit.read' permission. +func (s *AuthorizingStore) Get(ctx context.Context, id string) (*UnitMetadata, error) { + if err := s.checkPermission(ctx, "unit.read", id); err != nil { + return nil, err + } + return s.nextStore.Get(ctx, id) +} + +// Download checks for 'unit.read' permission. +func (s *AuthorizingStore) Download(ctx context.Context, id string) ([]byte, error) { + if err := s.checkPermission(ctx, "unit.read", id); err != nil { + return nil, err + } + return s.nextStore.Download(ctx, id) +} + +// Create checks for 'unit.write' permission. +func (s *AuthorizingStore) Create(ctx context.Context, id string) (*UnitMetadata, error) { + if err := s.checkPermission(ctx, "unit.write", id); err != nil { + return nil, err + } + return s.nextStore.Create(ctx, id) +} + +// Upload checks for 'unit.write' permission. +func (s *AuthorizingStore) Upload(ctx context.Context, id string, data []byte, lockID string) error { + if err := s.checkPermission(ctx, "unit.write", id); err != nil { + return err + } + return s.nextStore.Upload(ctx, id, data, lockID) +} + +// Delete checks for 'unit.delete' permission. +func (s *AuthorizingStore) Delete(ctx context.Context, id string) error { + if err := s.checkPermission(ctx, "unit.delete", id); err != nil { + return err + } + return s.nextStore.Delete(ctx, id) +} + +// Lock checks for 'unit.lock' permission. +func (s *AuthorizingStore) Lock(ctx context.Context, id string, info *LockInfo) error { + if err := s.checkPermission(ctx, "unit.lock", id); err != nil { + return err + } + err := s.nextStore.Lock(ctx, id, info) + if err != nil { + return err + } + + // Sync lock status to database + if err := s.queryStore.SyncUnitLock(ctx, id, info.ID, info.Who, info.Created); err != nil { + log.Printf("Warning: Failed to sync lock status for unit '%s': %v", id, err) + } + return nil +} + +// Unlock checks for 'unit.lock' permission. +func (s *AuthorizingStore) Unlock(ctx context.Context, id string, lockID string) error { + if err := s.checkPermission(ctx, "unit.lock", id); err != nil { + return err + } + err := s.nextStore.Unlock(ctx, id, lockID) + if err != nil { + return err + } + + // Sync unlock status to database + if err := s.queryStore.SyncUnitUnlock(ctx, id); err != nil { + log.Printf("Warning: Failed to sync unlock status for unit '%s': %v", id, err) + } + return nil +} + +// --- Other Pass-through Methods with Read Checks --- +func (s *AuthorizingStore) GetLock(ctx context.Context, id string) (*LockInfo, error) { + if err := s.checkPermission(ctx, "unit.read", id); err != nil { + return nil, err + } + return s.nextStore.GetLock(ctx, id) +} + +func (s *AuthorizingStore) ListVersions(ctx context.Context, id string) ([]*VersionInfo, error) { + if err := s.checkPermission(ctx, "unit.read", id); err != nil { + return nil, err + } + return s.nextStore.ListVersions(ctx, id) +} + +func (s *AuthorizingStore) RestoreVersion(ctx context.Context, id string, versionTimestamp time.Time, lockID string) error { + if err := s.checkPermission(ctx, "unit.write", id); err != nil { + return err + } + return s.nextStore.RestoreVersion(ctx, id, versionTimestamp, lockID) +} \ No newline at end of file diff --git a/taco/internal/storage/orchestrator.go b/taco/internal/storage/orchestrator.go new file mode 100644 index 000000000..0126a5aef --- /dev/null +++ b/taco/internal/storage/orchestrator.go @@ -0,0 +1,156 @@ +package storage + +import ( + "context" + "log" + "time" + + "github.com/diggerhq/digger/opentaco/internal/query" + "github.com/diggerhq/digger/opentaco/internal/query/types" +) + +// OrchestratingStore implements the UnitStore interface to coordinate a blob store +// (like S3) and a database index (like SQLite). +type OrchestratingStore struct { + blobStore UnitStore // The primary source of truth for file content (e.g., S3Store) + queryStore query.Store // The source of truth for metadata and listings +} + +// NewOrchestratingStore creates a new store that synchronizes blob and database storage. +func NewOrchestratingStore(blobStore UnitStore, queryStore query.Store) UnitStore { + return &OrchestratingStore{ + blobStore: blobStore, + queryStore: queryStore, + } +} + +// Create writes to the blob store first, then syncs the metadata to the database. +func (s *OrchestratingStore) Create(ctx context.Context, id string) (*UnitMetadata, error) { + meta, err := s.blobStore.Create(ctx, id) + if err != nil { + return nil, err // If blob storage fails, the whole operation fails. + } + + if err := s.queryStore.SyncEnsureUnit(ctx, id); err != nil { + log.Printf("CRITICAL: Unit '%s' created in blob storage but failed to sync to database: %v", id, err) + } else { + // Sync metadata too + if err := s.queryStore.SyncUnitMetadata(ctx, id, meta.Size, meta.Updated); err != nil { + log.Printf("Warning: Failed to sync metadata for unit '%s': %v", id, err) + } + } + return meta, nil +} + +// Upload writes to the blob store first, then syncs the metadata to the database. +func (s *OrchestratingStore) Upload(ctx context.Context, id string, data []byte, lockID string) error { + err := s.blobStore.Upload(ctx, id, data, lockID) + if err != nil { + return err + } + + // Get metadata to sync size + meta, err := s.blobStore.Get(ctx, id) + if err == nil && s.queryStore.IsEnabled() { + // Sync with full metadata + if syncer, ok := s.queryStore.(interface { + SyncUnitMetadata(context.Context, string, int64, time.Time) error + }); ok { + syncer.SyncUnitMetadata(ctx, id, meta.Size, meta.Updated) + } + } + + return nil +} + +// Delete removes from the blob store first, then syncs the deletion to the database. +func (s *OrchestratingStore) Delete(ctx context.Context, id string) error { + err := s.blobStore.Delete(ctx, id) + if err != nil { + return err + } + if err := s.queryStore.SyncDeleteUnit(ctx, id); err != nil { + log.Printf("CRITICAL: Unit '%s' deleted from blob storage but failed to sync to database: %v", id, err) + } + return nil +} + +// List bypasses blob storage and uses the fast database index. +func (s *OrchestratingStore) List(ctx context.Context, prefix string) ([]*UnitMetadata, error) { + var units []types.Unit + units, err := s.queryStore.ListUnits(ctx, prefix) + if err != nil { + return nil, err + } + + // Adapt the result from the query store's type to the storage's type. + metadata := make([]*UnitMetadata, len(units)) + for i, u := range units { + var lockInfo *LockInfo + if u.Locked { + lockInfo = &LockInfo{ + ID: u.LockID, + Who: u.LockWho, + Created: u.LockCreated, + } + } + metadata[i] = &UnitMetadata{ + ID: u.Name, + Size: u.Size, + Updated: u.UpdatedAt, + Locked: u.Locked, + LockInfo: lockInfo, + } + } + return metadata, nil +} + +// --- Pass-through methods --- +// For operations that only concern the blob data itself, we pass them directly +// to the underlying blob store. + +func (s *OrchestratingStore) Get(ctx context.Context, id string) (*UnitMetadata, error) { + return s.blobStore.Get(ctx, id) +} + +func (s *OrchestratingStore) Download(ctx context.Context, id string) ([]byte, error) { + return s.blobStore.Download(ctx, id) +} + +func (s *OrchestratingStore) Lock(ctx context.Context, id string, info *LockInfo) error { + err := s.blobStore.Lock(ctx, id, info) + if err != nil { + return err + } + + // Sync lock status to database + if err := s.queryStore.SyncUnitLock(ctx, id, info.ID, info.Who, info.Created); err != nil { + log.Printf("Warning: Failed to sync lock status for unit '%s': %v", id, err) + } + return nil +} + +func (s *OrchestratingStore) Unlock(ctx context.Context, id string, lockID string) error { + err := s.blobStore.Unlock(ctx, id, lockID) + if err != nil { + return err + } + + // Sync unlock status to database + if err := s.queryStore.SyncUnitUnlock(ctx, id); err != nil { + log.Printf("Warning: Failed to sync unlock status for unit '%s': %v", id, err) + } + return nil +} + +func (s *OrchestratingStore) GetLock(ctx context.Context, id string) (*LockInfo, error) { + return s.blobStore.GetLock(ctx, id) +} + +func (s *OrchestratingStore) ListVersions(ctx context.Context, id string) ([]*VersionInfo, error) { + return s.blobStore.ListVersions(ctx, id) +} + +func (s *OrchestratingStore) RestoreVersion(ctx context.Context, id string, versionTimestamp time.Time, lockID string) error { + return s.blobStore.RestoreVersion(ctx, id, versionTimestamp, lockID) +} \ No newline at end of file diff --git a/taco/internal/unit/handler.go b/taco/internal/unit/handler.go index 4edbea4ee..356f14054 100644 --- a/taco/internal/unit/handler.go +++ b/taco/internal/unit/handler.go @@ -12,8 +12,11 @@ import ( "github.com/diggerhq/digger/opentaco/internal/deps" "github.com/diggerhq/digger/opentaco/internal/rbac" "github.com/diggerhq/digger/opentaco/internal/storage" + "github.com/diggerhq/digger/opentaco/internal/query" "github.com/google/uuid" "github.com/labstack/echo/v4" + "log" + ) // Handler serves the management API (unit CRUD and locking) @@ -21,13 +24,15 @@ type Handler struct { store storage.UnitStore rbacManager *rbac.RBACManager signer *auth.Signer + queryStore query.Store } -func NewHandler(store storage.UnitStore, rbacManager *rbac.RBACManager, signer *auth.Signer) *Handler { +func NewHandler(store storage.UnitStore, rbacManager *rbac.RBACManager, signer *auth.Signer, queryStore query.Store) *Handler { return &Handler{ store: store, rbacManager: rbacManager, signer: signer, + queryStore: queryStore, } } @@ -68,50 +73,36 @@ func (h *Handler) CreateUnit(c echo.Context) error { } func (h *Handler) ListUnits(c echo.Context) error { - prefix := c.QueryParam("prefix") - items, err := h.store.List(c.Request().Context(), prefix) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to list units"}) - } - - var units []*domain.Unit - unitIDs := make([]string, 0, len(items)) - unitMap := make(map[string]*storage.UnitMetadata) - - // Collect all unit IDs and create a map for quick lookup - for _, s := range items { - unitIDs = append(unitIDs, s.ID) - unitMap[s.ID] = s - } - - // Filter units by RBAC permissions if available - if h.rbacManager != nil && h.signer != nil { - principal, err := h.getPrincipalFromToken(c) - if err != nil { - // If we can't get principal, check if RBAC is enabled - if enabled, _ := h.rbacManager.IsEnabled(c.Request().Context()); enabled { - return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Failed to authenticate user"}) - } - // RBAC not enabled, show all units - } else { - // Filter units based on read permissions - filteredIDs, err := h.rbacManager.FilterUnitsByReadAccess(c.Request().Context(), principal, unitIDs) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to check permissions"}) - } - unitIDs = filteredIDs - } - } - - // Build response with filtered units - for _, id := range unitIDs { - if s, exists := unitMap[id]; exists { - units = append(units, &domain.Unit{ID: s.ID, Size: s.Size, Updated: s.Updated, Locked: s.Locked, LockInfo: convertLockInfo(s.LockInfo)}) - } - } - - domain.SortUnitsByID(units) - return c.JSON(http.StatusOK, map[string]interface{}{"units": units, "count": len(units)}) + ctx := c.Request().Context() + prefix := c.QueryParam("prefix") + + + unitsMetadata, err := h.store.List(ctx, prefix) + if err != nil { + if err.Error() == "unauthorized" || err.Error() == "forbidden" { + return c.JSON(http.StatusForbidden, map[string]string{"error": err.Error()}) + } + log.Printf("Error listing units: %v", err) + return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to list units"}) + } + + // The list is already filtered and secure. We just build the response. + domainUnits := make([]*domain.Unit, 0, len(unitsMetadata)) + for _, u := range unitsMetadata { + domainUnits = append(domainUnits, &domain.Unit{ + ID: u.ID, + Size: u.Size, + Updated: u.Updated, + Locked: u.Locked, + LockInfo: convertLockInfo(u.LockInfo), + }) + } + domain.SortUnitsByID(domainUnits) + + return c.JSON(http.StatusOK, map[string]interface{}{ + "units": domainUnits, + "count": len(domainUnits), + }) } func (h *Handler) GetUnit(c echo.Context) error { @@ -120,8 +111,12 @@ func (h *Handler) GetUnit(c echo.Context) error { if err := domain.ValidateUnitID(id); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } + metadata, err := h.store.Get(c.Request().Context(), id) if err != nil { + if err.Error() == "forbidden" { + return c.JSON(http.StatusForbidden, map[string]string{"error": "Forbidden"}) + } if err == storage.ErrNotFound { return c.JSON(http.StatusNotFound, map[string]string{"error": "Unit not found"}) } diff --git a/taco/internal/wiring/rbac.go b/taco/internal/wiring/rbac.go new file mode 100644 index 000000000..52ee3b1a6 --- /dev/null +++ b/taco/internal/wiring/rbac.go @@ -0,0 +1,69 @@ +package wiring + +import ( + "context" + "log" + + "github.com/diggerhq/digger/opentaco/internal/query" + "github.com/diggerhq/digger/opentaco/internal/rbac" + "github.com/diggerhq/digger/opentaco/internal/storage" +) + +// SyncRBACFromStorage syncs RBAC data from storage to the query database. +// This is called at startup to populate the database with roles, permissions, and users. +func SyncRBACFromStorage(ctx context.Context, store storage.UnitStore, queryStore query.Store) error { + // Check if it's S3 storage + s3Store, ok := store.(storage.S3Store) + if !ok { + log.Println("RBAC sync skipped: storage backend does not support RBAC") + return nil + } + + // Create the S3 RBAC store + rbacStore := rbac.NewS3RBACStore( + s3Store.GetS3Client(), + s3Store.GetS3Bucket(), + s3Store.GetS3Prefix(), + ) + + log.Println("Starting RBAC data sync from S3 to database...") + + // Sync permissions + permissions, err := rbacStore.ListPermissions(ctx) + if err != nil { + return err + } + for _, perm := range permissions { + if err := queryStore.SyncPermission(ctx, perm); err != nil { + log.Printf("Warning: Failed to sync permission %s: %v", perm.ID, err) + } + } + log.Printf("Synced %d permissions", len(permissions)) + + // Sync roles + roles, err := rbacStore.ListRoles(ctx) + if err != nil { + return err + } + for _, role := range roles { + if err := queryStore.SyncRole(ctx, role); err != nil { + log.Printf("Warning: Failed to sync role %s: %v", role.ID, err) + } + } + log.Printf("Synced %d roles", len(roles)) + + // Sync users + users, err := rbacStore.ListUserAssignments(ctx) + if err != nil { + return err + } + for _, user := range users { + if err := queryStore.SyncUser(ctx, user); err != nil { + log.Printf("Warning: Failed to sync user %s: %v", user.Subject, err) + } + } + log.Printf("Synced %d user assignments", len(users)) + + log.Println("RBAC data sync completed successfully") + return nil +} diff --git a/taco/providers/terraform/opentaco/go.mod b/taco/providers/terraform/opentaco/go.mod index f346d8451..1918eea70 100644 --- a/taco/providers/terraform/opentaco/go.mod +++ b/taco/providers/terraform/opentaco/go.mod @@ -3,13 +3,34 @@ module github.com/diggerhq/digger/opentaco/providers/terraform/opentaco go 1.24 require ( + github.com/diggerhq/digger/opentaco/internal v0.0.0 github.com/diggerhq/digger/opentaco/pkg/sdk v0.0.0 github.com/hashicorp/terraform-plugin-framework v1.5.0 + github.com/mr-tron/base58 v1.2.0 ) require ( + github.com/aws/aws-sdk-go-v2 v1.38.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.31.2 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.4 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.87.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect + github.com/aws/smithy-go v1.22.5 // indirect github.com/fatih/color v1.13.0 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect @@ -18,19 +39,20 @@ require ( github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect github.com/oklog/run v1.1.0 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect google.golang.org/grpc v1.60.0 // indirect google.golang.org/protobuf v1.31.0 // indirect ) +replace github.com/diggerhq/digger/opentaco/internal => ../../../internal + replace github.com/diggerhq/digger/opentaco/pkg/sdk => ../../../pkg/sdk diff --git a/taco/providers/terraform/opentaco/go.sum b/taco/providers/terraform/opentaco/go.sum index 5931a7eec..193ab0b05 100644 --- a/taco/providers/terraform/opentaco/go.sum +++ b/taco/providers/terraform/opentaco/go.sum @@ -1,16 +1,55 @@ +github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8= +github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0/go.mod h1:/mXlTIVG9jbxkqDnr5UQNQxW1HRYxeGklkM9vAFeabg= +github.com/aws/aws-sdk-go-v2/config v1.31.2 h1:NOaSZpVGEH2Np/c1toSeW0jooNl+9ALmsUTZ8YvkJR0= +github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes= +github.com/aws/aws-sdk-go-v2/credentials v1.18.6 h1:AmmvNEYrru7sYNJnp3pf57lGbiarX4T9qU/6AZ9SucU= +github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 h1:lpdMwTzmuDLkgW7086jE94HweHCqG+uOJwHf3LZs7T0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.4 h1:BE/MNQ86yzTINrfxPPFS86QCBNQeLKY2A0KhDh47+wI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.4/go.mod h1:SPBBhkJxjcrzJBc+qY85e83MQ2q3qdra8fghhkkyrJg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.4 h1:Beh9oVgtQnBgR4sKKzkUBRQpf1GnL4wt0l4s8h2VCJ0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.4/go.mod h1:b17At0o8inygF+c6FOD3rNyYZufPw62o9XJbSfQPgbo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.4 h1:HVSeukL40rHclNcUqVcBwE1YoZhOkoLeBfhUqR3tjIU= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.4/go.mod h1:DnbBOv4FlIXHj2/xmrUQYtawRFC9L9ZmQPz+DBc6X5I= +github.com/aws/aws-sdk-go-v2/service/s3 v1.87.1 h1:2n6Pd67eJwAb/5KCX62/8RTU0aFAAW7V5XIGSghiHrw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.87.1/go.mod h1:w5PC+6GHLkvMJKasYGVloB3TduOtROEMqm15HSuIbw4= +github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0= +github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKSQkyQQ9ftnl/LttQOcGI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo= +github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= +github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= @@ -32,11 +71,14 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -46,23 +88,26 @@ github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DV github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY=