diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 44060f6..1f787e3 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -20,42 +20,43 @@ include an indication of the Open Source License under which that package is distributed. For any package *NOT* distributed under the terms of the Apache License version 2.0, we include the full text of the package's License below. +* `github.com/aws-controllers-k8s/ec2-controller` +* `github.com/aws-controllers-k8s/iam-controller` +* `github.com/aws-controllers-k8s/kms-controller` * `github.com/aws-controllers-k8s/runtime` +* `github.com/aws-controllers-k8s/s3-controller` * `github.com/aws/aws-sdk-go` +* `github.com/aws/aws-sdk-go-v2` +* `github.com/aws/aws-sdk-go-v2/service/mwaaserverless` +* `github.com/aws/smithy-go` +* `github.com/go-logr/logr` * `github.com/spf13/pflag` +* `k8s.io/api` * `k8s.io/apimachinery` * `k8s.io/client-go` * `sigs.k8s.io/controller-runtime` -### github.com/aws-controllers-k8s/runtime +### github.com/aws-controllers-k8s/ec2-controller License Identifier: Apache-2.0 Subdependencies: +* `github.com/aws-controllers-k8s/iam-controller` +* `github.com/aws-controllers-k8s/runtime` +* `github.com/aws/aws-sdk-go` * `github.com/aws/aws-sdk-go-v2` -* `github.com/aws/aws-sdk-go-v2/config` -* `github.com/aws/aws-sdk-go-v2/credentials` -* `github.com/aws/aws-sdk-go-v2/service/sts` +* `github.com/aws/aws-sdk-go-v2/service/ec2` * `github.com/aws/smithy-go` -* `github.com/cenkalti/backoff/v4` * `github.com/go-logr/logr` -* `github.com/go-logr/zapr` -* `github.com/google/go-cmp` -* `github.com/itchyny/gojq` -* `github.com/jaypipes/envutil` -* `github.com/micahhausler/aws-iam-policy` -* `github.com/pkg/errors` -* `github.com/prometheus/client_golang` +* `github.com/samber/lo` * `github.com/spf13/pflag` * `github.com/stretchr/testify` -* `go.uber.org/zap` * `k8s.io/api` * `k8s.io/apimachinery` * `k8s.io/client-go` -* `k8s.io/klog/v2` -* `k8s.io/utils` * `sigs.k8s.io/controller-runtime` -* `sigs.k8s.io/yaml` +* `github.com/aws/aws-sdk-go-v2/config` +* `github.com/aws/aws-sdk-go-v2/credentials` * `github.com/aws/aws-sdk-go-v2/feature/ec2/imds` * `github.com/aws/aws-sdk-go-v2/internal/configsources` * `github.com/aws/aws-sdk-go-v2/internal/endpoints/v2` @@ -64,36 +65,45 @@ Subdependencies: * `github.com/aws/aws-sdk-go-v2/service/internal/presigned-url` * `github.com/aws/aws-sdk-go-v2/service/sso` * `github.com/aws/aws-sdk-go-v2/service/ssooidc` +* `github.com/aws/aws-sdk-go-v2/service/sts` * `github.com/beorn7/perks` +* `github.com/cenkalti/backoff/v4` * `github.com/cespare/xxhash/v2` * `github.com/davecgh/go-spew` * `github.com/emicklei/go-restful/v3` -* `github.com/evanphx/json-patch` * `github.com/evanphx/json-patch/v5` * `github.com/fsnotify/fsnotify` * `github.com/fxamacker/cbor/v2` +* `github.com/go-logr/zapr` * `github.com/go-openapi/jsonpointer` * `github.com/go-openapi/jsonreference` * `github.com/go-openapi/swag` * `github.com/google/btree` * `github.com/google/gnostic-models` +* `github.com/google/go-cmp` * `github.com/google/uuid` +* `github.com/itchyny/gojq` * `github.com/itchyny/timefmt-go` +* `github.com/jaypipes/envutil` * `github.com/josharian/intern` * `github.com/json-iterator/go` * `github.com/mailru/easyjson` +* `github.com/micahhausler/aws-iam-policy` * `github.com/modern-go/concurrent` * `github.com/modern-go/reflect2` * `github.com/munnerz/goautoneg` +* `github.com/pkg/errors` * `github.com/pmezard/go-difflib` +* `github.com/prometheus/client_golang` * `github.com/prometheus/client_model` * `github.com/prometheus/common` * `github.com/prometheus/procfs` -* `github.com/stretchr/objx` * `github.com/x448/float16` * `go.uber.org/multierr` +* `go.uber.org/zap` * `go.yaml.in/yaml/v2` * `go.yaml.in/yaml/v3` +* `golang.org/x/exp` * `golang.org/x/net` * `golang.org/x/oauth2` * `golang.org/x/sync` @@ -107,103 +117,49 @@ Subdependencies: * `gopkg.in/inf.v0` * `gopkg.in/yaml.v3` * `k8s.io/apiextensions-apiserver` +* `k8s.io/klog/v2` * `k8s.io/kube-openapi` +* `k8s.io/utils` * `sigs.k8s.io/json` * `sigs.k8s.io/randfill` * `sigs.k8s.io/structured-merge-diff/v6` +* `sigs.k8s.io/yaml` -#### github.com/aws/aws-sdk-go-v2 +#### github.com/aws-controllers-k8s/iam-controller License Identifier: Apache-2.0 -#### github.com/aws/aws-sdk-go-v2/config +#### github.com/aws-controllers-k8s/runtime License Identifier: Apache-2.0 -#### github.com/aws/aws-sdk-go-v2/credentials +#### github.com/aws/aws-sdk-go License Identifier: Apache-2.0 -#### github.com/aws/aws-sdk-go-v2/service/sts +#### github.com/aws/aws-sdk-go-v2 License Identifier: Apache-2.0 -#### github.com/aws/smithy-go +#### github.com/aws/aws-sdk-go-v2/service/ec2 License Identifier: Apache-2.0 -#### github.com/cenkalti/backoff/v4 - -License Identifier: MIT - -The MIT License (MIT) - -Copyright (c) 2014 Cenk Altı - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#### github.com/go-logr/logr +#### github.com/aws/smithy-go License Identifier: Apache-2.0 -#### github.com/go-logr/zapr +#### github.com/go-logr/logr License Identifier: Apache-2.0 -#### github.com/google/go-cmp - -License Identifier: BSD-3-Clause - -Copyright (c) 2017 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#### github.com/itchyny/gojq +#### github.com/samber/lo License Identifier: MIT -The MIT License (MIT) +MIT License -Copyright (c) 2019-2021 itchyny +Copyright (c) 2022 Samuel Berthe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -223,56 +179,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#### github.com/jaypipes/envutil - -License Identifier: Apache-2.0 - -#### github.com/micahhausler/aws-iam-policy - -License Identifier: MIT - -The MIT License (MIT) - -Copyright (c) 2023, Micah Hausler - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#### github.com/pkg/errors - -License Identifier: BSD-2-Clause - -Copyright (c) 2015, Dave Cheney -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#### github.com/prometheus/client_golang - -License Identifier: Apache-2.0 - #### github.com/spf13/pflag License Identifier: BSD-3-Clause @@ -332,28 +238,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#### go.uber.org/zap - -Copyright (c) 2016-2017 Uber Technologies, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - #### k8s.io/api License Identifier: Apache-2.0 @@ -366,19 +250,15 @@ License Identifier: Apache-2.0 License Identifier: Apache-2.0 -#### k8s.io/klog/v2 - -License Identifier: Apache-2.0 - -#### k8s.io/utils +#### sigs.k8s.io/controller-runtime License Identifier: Apache-2.0 -#### sigs.k8s.io/controller-runtime +#### github.com/aws/aws-sdk-go-v2/config License Identifier: Apache-2.0 -#### sigs.k8s.io/yaml +#### github.com/aws/aws-sdk-go-v2/credentials License Identifier: Apache-2.0 @@ -414,6 +294,10 @@ License Identifier: Apache-2.0 License Identifier: Apache-2.0 +#### github.com/aws/aws-sdk-go-v2/service/sts + +License Identifier: Apache-2.0 + #### github.com/beorn7/perks Copyright (C) 2013 Blake Mizerany @@ -437,6 +321,31 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#### github.com/cenkalti/backoff/v4 + +License Identifier: MIT + +The MIT License (MIT) + +Copyright (c) 2014 Cenk Altı + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + #### github.com/cespare/xxhash/v2 License Identifier: MIT @@ -511,36 +420,6 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#### github.com/evanphx/json-patch - -License Identifier: BSD-3-Clause - -Copyright (c) 2014, Evan Phoenix -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -* Neither the name of the Evan Phoenix nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - #### github.com/evanphx/json-patch/v5 License Identifier: BSD-3-Clause @@ -627,6 +506,10 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#### github.com/go-logr/zapr + +License Identifier: Apache-2.0 + #### github.com/go-openapi/jsonpointer License Identifier: Apache-2.0 @@ -647,11 +530,11 @@ License Identifier: Apache-2.0 License Identifier: Apache-2.0 -#### github.com/google/uuid +#### github.com/google/go-cmp License Identifier: BSD-3-Clause -Copyright (c) 2009,2014 Google Inc. All rights reserved. +Copyright (c) 2017 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -679,7 +562,65 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#### github.com/itchyny/timefmt-go +#### github.com/google/uuid + +License Identifier: BSD-3-Clause + +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#### github.com/itchyny/gojq + +License Identifier: MIT + +The MIT License (MIT) + +Copyright (c) 2019-2021 itchyny + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +#### github.com/itchyny/timefmt-go License Identifier: MIT @@ -705,6 +646,10 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#### github.com/jaypipes/envutil + +License Identifier: Apache-2.0 + #### github.com/josharian/intern License Identifier: MIT @@ -767,6 +712,20 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#### github.com/micahhausler/aws-iam-policy + +License Identifier: MIT + +The MIT License (MIT) + +Copyright (c) 2023, Micah Hausler + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + #### github.com/modern-go/concurrent License Identifier: Apache-2.0 @@ -811,6 +770,34 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#### github.com/pkg/errors + +License Identifier: BSD-2-Clause + +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + #### github.com/pmezard/go-difflib License Identifier: BSD-3-Clause @@ -843,6 +830,10 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#### github.com/prometheus/client_golang + +License Identifier: Apache-2.0 + #### github.com/prometheus/client_model License Identifier: Apache-2.0 @@ -855,14 +846,13 @@ License Identifier: Apache-2.0 License Identifier: Apache-2.0 -#### github.com/stretchr/objx +#### github.com/x448/float16 License Identifier: MIT -The MIT License +MIT License -Copyright (c) 2014 Stretchr, Inc. -Copyright (c) 2017-2018 objx contributors +Copyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -882,13 +872,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#### github.com/x448/float16 - -License Identifier: MIT - -MIT License +#### go.uber.org/multierr -Copyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker +Copyright (c) 2017-2021 Uber Technologies, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -897,20 +883,20 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. -#### go.uber.org/multierr +#### go.uber.org/zap -Copyright (c) 2017-2021 Uber Technologies, Inc. +Copyright (c) 2016-2017 Uber Technologies, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -938,6 +924,38 @@ License Identifier: Apache-2.0 License Identifier: Apache-2.0 +#### golang.org/x/exp + +License Identifier: BSD-3-Clause + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + #### golang.org/x/net License Identifier: BSD-3-Clause @@ -1269,10 +1287,18 @@ License Identifier: Apache-2.0 License Identifier: Apache-2.0 +#### k8s.io/klog/v2 + +License Identifier: Apache-2.0 + #### k8s.io/kube-openapi License Identifier: Apache-2.0 +#### k8s.io/utils + +License Identifier: Apache-2.0 + #### sigs.k8s.io/json License Identifier: BSD-3-Clause @@ -1524,17 +1550,491 @@ License Identifier: Apache-2.0 License Identifier: Apache-2.0 -### github.com/aws/aws-sdk-go +#### sigs.k8s.io/yaml + +License Identifier: Apache-2.0 + +### github.com/aws-controllers-k8s/iam-controller License Identifier: Apache-2.0 Subdependencies: +* `github.com/aws-controllers-k8s/runtime` +* `github.com/aws/aws-sdk-go` +* `github.com/aws/aws-sdk-go-v2` +* `github.com/aws/aws-sdk-go-v2/service/iam` +* `github.com/aws/smithy-go` +* `github.com/go-logr/logr` +* `github.com/samber/lo` +* `github.com/spf13/pflag` +* `github.com/stretchr/testify` +* `k8s.io/api` +* `k8s.io/apimachinery` +* `k8s.io/client-go` +* `sigs.k8s.io/controller-runtime` +* `github.com/aws/aws-sdk-go-v2/config` +* `github.com/aws/aws-sdk-go-v2/credentials` +* `github.com/aws/aws-sdk-go-v2/feature/ec2/imds` +* `github.com/aws/aws-sdk-go-v2/internal/configsources` +* `github.com/aws/aws-sdk-go-v2/internal/endpoints/v2` +* `github.com/aws/aws-sdk-go-v2/internal/ini` +* `github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding` +* `github.com/aws/aws-sdk-go-v2/service/internal/presigned-url` +* `github.com/aws/aws-sdk-go-v2/service/sso` +* `github.com/aws/aws-sdk-go-v2/service/ssooidc` +* `github.com/aws/aws-sdk-go-v2/service/sts` +* `github.com/beorn7/perks` +* `github.com/cenkalti/backoff/v4` +* `github.com/cespare/xxhash/v2` +* `github.com/davecgh/go-spew` +* `github.com/emicklei/go-restful/v3` +* `github.com/evanphx/json-patch/v5` +* `github.com/fsnotify/fsnotify` +* `github.com/fxamacker/cbor/v2` +* `github.com/go-logr/zapr` +* `github.com/go-openapi/jsonpointer` +* `github.com/go-openapi/jsonreference` +* `github.com/go-openapi/swag` +* `github.com/google/btree` +* `github.com/google/gnostic-models` +* `github.com/google/go-cmp` +* `github.com/google/uuid` +* `github.com/itchyny/gojq` +* `github.com/itchyny/timefmt-go` +* `github.com/jaypipes/envutil` * `github.com/jmespath/go-jmespath` +* `github.com/josharian/intern` +* `github.com/json-iterator/go` +* `github.com/mailru/easyjson` +* `github.com/micahhausler/aws-iam-policy` +* `github.com/modern-go/concurrent` +* `github.com/modern-go/reflect2` +* `github.com/munnerz/goautoneg` +* `github.com/pkg/errors` +* `github.com/pmezard/go-difflib` +* `github.com/prometheus/client_golang` +* `github.com/prometheus/client_model` +* `github.com/prometheus/common` +* `github.com/prometheus/procfs` +* `github.com/x448/float16` +* `go.uber.org/multierr` +* `go.uber.org/zap` +* `go.yaml.in/yaml/v2` +* `go.yaml.in/yaml/v3` +* `golang.org/x/exp` +* `golang.org/x/net` +* `golang.org/x/oauth2` +* `golang.org/x/sync` +* `golang.org/x/sys` +* `golang.org/x/term` +* `golang.org/x/text` +* `golang.org/x/time` +* `gomodules.xyz/jsonpatch/v2` +* `google.golang.org/protobuf` +* `gopkg.in/evanphx/json-patch.v4` +* `gopkg.in/inf.v0` +* `gopkg.in/yaml.v3` +* `k8s.io/apiextensions-apiserver` +* `k8s.io/klog/v2` +* `k8s.io/kube-openapi` +* `k8s.io/utils` +* `sigs.k8s.io/json` +* `sigs.k8s.io/randfill` +* `sigs.k8s.io/structured-merge-diff/v6` +* `sigs.k8s.io/yaml` -#### github.com/jmespath/go-jmespath + + + + +#### github.com/aws/aws-sdk-go-v2 + +License Identifier: Apache-2.0 + +#### github.com/aws/aws-sdk-go-v2/service/iam + +License Identifier: Apache-2.0 + +#### github.com/aws/smithy-go + +License Identifier: Apache-2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#### github.com/jmespath/go-jmespath + +License Identifier: Apache-2.0 + +### github.com/aws-controllers-k8s/kms-controller + +License Identifier: Apache-2.0 + +Subdependencies: +* `github.com/aws-controllers-k8s/runtime` +* `github.com/aws/aws-sdk-go` +* `github.com/aws/aws-sdk-go-v2` +* `github.com/aws/aws-sdk-go-v2/service/kms` +* `github.com/aws/smithy-go` +* `github.com/go-logr/logr` +* `github.com/spf13/pflag` +* `github.com/stretchr/testify` +* `k8s.io/api` +* `k8s.io/apimachinery` +* `k8s.io/client-go` +* `sigs.k8s.io/controller-runtime` +* `github.com/aws/aws-sdk-go-v2/config` +* `github.com/aws/aws-sdk-go-v2/credentials` +* `github.com/aws/aws-sdk-go-v2/feature/ec2/imds` +* `github.com/aws/aws-sdk-go-v2/internal/configsources` +* `github.com/aws/aws-sdk-go-v2/internal/endpoints/v2` +* `github.com/aws/aws-sdk-go-v2/internal/ini` +* `github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding` +* `github.com/aws/aws-sdk-go-v2/service/internal/presigned-url` +* `github.com/aws/aws-sdk-go-v2/service/sso` +* `github.com/aws/aws-sdk-go-v2/service/ssooidc` +* `github.com/aws/aws-sdk-go-v2/service/sts` +* `github.com/beorn7/perks` +* `github.com/cenkalti/backoff/v4` +* `github.com/cespare/xxhash/v2` +* `github.com/davecgh/go-spew` +* `github.com/emicklei/go-restful/v3` +* `github.com/evanphx/json-patch/v5` +* `github.com/fsnotify/fsnotify` +* `github.com/fxamacker/cbor/v2` +* `github.com/go-logr/zapr` +* `github.com/go-openapi/jsonpointer` +* `github.com/go-openapi/jsonreference` +* `github.com/go-openapi/swag` +* `github.com/google/btree` +* `github.com/google/gnostic-models` +* `github.com/google/go-cmp` +* `github.com/google/uuid` +* `github.com/itchyny/gojq` +* `github.com/itchyny/timefmt-go` +* `github.com/jaypipes/envutil` +* `github.com/josharian/intern` +* `github.com/json-iterator/go` +* `github.com/mailru/easyjson` +* `github.com/micahhausler/aws-iam-policy` +* `github.com/modern-go/concurrent` +* `github.com/modern-go/reflect2` +* `github.com/munnerz/goautoneg` +* `github.com/pkg/errors` +* `github.com/pmezard/go-difflib` +* `github.com/prometheus/client_golang` +* `github.com/prometheus/client_model` +* `github.com/prometheus/common` +* `github.com/prometheus/procfs` +* `github.com/x448/float16` +* `go.uber.org/multierr` +* `go.uber.org/zap` +* `go.yaml.in/yaml/v2` +* `go.yaml.in/yaml/v3` +* `golang.org/x/net` +* `golang.org/x/oauth2` +* `golang.org/x/sync` +* `golang.org/x/sys` +* `golang.org/x/term` +* `golang.org/x/text` +* `golang.org/x/time` +* `gomodules.xyz/jsonpatch/v2` +* `google.golang.org/protobuf` +* `gopkg.in/evanphx/json-patch.v4` +* `gopkg.in/inf.v0` +* `gopkg.in/yaml.v3` +* `k8s.io/apiextensions-apiserver` +* `k8s.io/klog/v2` +* `k8s.io/kube-openapi` +* `k8s.io/utils` +* `sigs.k8s.io/json` +* `sigs.k8s.io/randfill` +* `sigs.k8s.io/structured-merge-diff/v6` +* `sigs.k8s.io/yaml` + + + + + + + +#### github.com/aws/aws-sdk-go-v2/service/kms + +License Identifier: Apache-2.0 + + + +### github.com/aws-controllers-k8s/s3-controller + +License Identifier: Apache-2.0 + +Subdependencies: +* `github.com/aws-controllers-k8s/iam-controller` +* `github.com/aws-controllers-k8s/runtime` +* `github.com/aws/aws-sdk-go` +* `github.com/aws/aws-sdk-go-v2` +* `github.com/aws/aws-sdk-go-v2/service/s3` +* `github.com/aws/aws-sdk-go-v2/service/s3control` +* `github.com/aws/smithy-go` +* `github.com/go-logr/logr` +* `github.com/pkg/errors` +* `github.com/spf13/pflag` +* `github.com/stretchr/testify` +* `k8s.io/api` +* `k8s.io/apimachinery` +* `k8s.io/client-go` +* `sigs.k8s.io/controller-runtime` +* `github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream` +* `github.com/aws/aws-sdk-go-v2/config` +* `github.com/aws/aws-sdk-go-v2/credentials` +* `github.com/aws/aws-sdk-go-v2/feature/ec2/imds` +* `github.com/aws/aws-sdk-go-v2/internal/configsources` +* `github.com/aws/aws-sdk-go-v2/internal/endpoints/v2` +* `github.com/aws/aws-sdk-go-v2/internal/ini` +* `github.com/aws/aws-sdk-go-v2/internal/v4a` +* `github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding` +* `github.com/aws/aws-sdk-go-v2/service/internal/checksum` +* `github.com/aws/aws-sdk-go-v2/service/internal/presigned-url` +* `github.com/aws/aws-sdk-go-v2/service/internal/s3shared` +* `github.com/aws/aws-sdk-go-v2/service/sso` +* `github.com/aws/aws-sdk-go-v2/service/ssooidc` +* `github.com/aws/aws-sdk-go-v2/service/sts` +* `github.com/beorn7/perks` +* `github.com/cenkalti/backoff/v4` +* `github.com/cespare/xxhash/v2` +* `github.com/davecgh/go-spew` +* `github.com/emicklei/go-restful/v3` +* `github.com/evanphx/json-patch/v5` +* `github.com/fsnotify/fsnotify` +* `github.com/fxamacker/cbor/v2` +* `github.com/go-logr/zapr` +* `github.com/go-openapi/jsonpointer` +* `github.com/go-openapi/jsonreference` +* `github.com/go-openapi/swag` +* `github.com/google/btree` +* `github.com/google/gnostic-models` +* `github.com/google/go-cmp` +* `github.com/google/uuid` +* `github.com/itchyny/gojq` +* `github.com/itchyny/timefmt-go` +* `github.com/jaypipes/envutil` +* `github.com/josharian/intern` +* `github.com/json-iterator/go` +* `github.com/mailru/easyjson` +* `github.com/micahhausler/aws-iam-policy` +* `github.com/modern-go/concurrent` +* `github.com/modern-go/reflect2` +* `github.com/munnerz/goautoneg` +* `github.com/pmezard/go-difflib` +* `github.com/prometheus/client_golang` +* `github.com/prometheus/client_model` +* `github.com/prometheus/common` +* `github.com/prometheus/procfs` +* `github.com/x448/float16` +* `go.uber.org/multierr` +* `go.uber.org/zap` +* `go.yaml.in/yaml/v2` +* `go.yaml.in/yaml/v3` +* `golang.org/x/net` +* `golang.org/x/oauth2` +* `golang.org/x/sync` +* `golang.org/x/sys` +* `golang.org/x/term` +* `golang.org/x/text` +* `golang.org/x/time` +* `gomodules.xyz/jsonpatch/v2` +* `google.golang.org/protobuf` +* `gopkg.in/evanphx/json-patch.v4` +* `gopkg.in/inf.v0` +* `gopkg.in/yaml.v3` +* `k8s.io/apiextensions-apiserver` +* `k8s.io/klog/v2` +* `k8s.io/kube-openapi` +* `k8s.io/utils` +* `sigs.k8s.io/json` +* `sigs.k8s.io/randfill` +* `sigs.k8s.io/structured-merge-diff/v6` +* `sigs.k8s.io/yaml` + +#### github.com/aws-controllers-k8s/iam-controller + +License Identifier: Apache-2.0 + + + + + +#### github.com/aws/aws-sdk-go-v2 + +License Identifier: Apache-2.0 + +#### github.com/aws/aws-sdk-go-v2/service/s3 + +License Identifier: Apache-2.0 + +#### github.com/aws/aws-sdk-go-v2/service/s3control + +License Identifier: Apache-2.0 + +#### github.com/aws/smithy-go + +License Identifier: Apache-2.0 + + + + + + + + + + + + + + + + + +#### github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream + +License Identifier: Apache-2.0 + + + + + + + + + + + + + +#### github.com/aws/aws-sdk-go-v2/internal/v4a + +License Identifier: Apache-2.0 + + + +#### github.com/aws/aws-sdk-go-v2/service/internal/checksum + +License Identifier: Apache-2.0 + + + +#### github.com/aws/aws-sdk-go-v2/service/internal/s3shared + +License Identifier: Apache-2.0 + +### github.com/aws/aws-sdk-go + +License Identifier: Apache-2.0 + +Subdependencies: +* `github.com/jmespath/go-jmespath` + +### github.com/aws/aws-sdk-go-v2 + +License Identifier: Apache-2.0 + +Subdependencies: +* `github.com/aws/smithy-go` + +#### github.com/aws/smithy-go + +License Identifier: Apache-2.0 + +### github.com/aws/aws-sdk-go-v2/service/mwaaserverless License Identifier: Apache-2.0 +Subdependencies: +* `github.com/aws/aws-sdk-go-v2` +* `github.com/aws/aws-sdk-go-v2/internal/configsources` +* `github.com/aws/aws-sdk-go-v2/internal/endpoints/v2` +* `github.com/aws/smithy-go` + + + + + + + diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 366b801..5eddc9a 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,13 +1,13 @@ ack_generate_info: - build_date: "2026-07-13T18:36:33Z" + build_date: "2026-07-21T20:02:26Z" build_hash: 01fb153549af6a2df3747478105143e989ff4355 go_version: go1.25.0 - version: v0.60.0-7-g01fb153 -api_directory_checksum: d3137e7b874e92eeec596f22d86192a74a244409 + version: v0.61.0-1-g01fb153 +api_directory_checksum: 90e12cbe8c76d4bf443051923203ad1c479b5710 api_version: v1alpha1 -aws_sdk_go_version: v1.42.0 +aws_sdk_go_version: v1.42.1 generator_config_info: - file_checksum: 7a5ea9c886c80beb718544ec22f6d5a38fc4aca3 + file_checksum: c03d838a63196bdce030590d3395dc99e07e33bb original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/enums.go b/apis/v1alpha1/enums.go index 63cdeab..18baaf4 100644 --- a/apis/v1alpha1/enums.go +++ b/apis/v1alpha1/enums.go @@ -70,9 +70,9 @@ const ( WorkflowRunStatus_TIMEOUT WorkflowRunStatus = "TIMEOUT" ) -type WorkflowStatus string +type WorkflowStatus_SDK string const ( - WorkflowStatus_DELETING WorkflowStatus = "DELETING" - WorkflowStatus_READY WorkflowStatus = "READY" + WorkflowStatus_SDK_DELETING WorkflowStatus_SDK = "DELETING" + WorkflowStatus_SDK_READY WorkflowStatus_SDK = "READY" ) diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index f547e99..646169f 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -1,5 +1,106 @@ sdk_names: model_name: mwaa-serverless ignore: - resource_names: - - Workflow + field_paths: + # Warnings/RevisionId/IsLatestVersion are transient create/update response + # metadata, not part of the desired or observed resource state, so there is + # nothing for the controller to reconcile against. + - CreateWorkflowOutput.Warnings + - CreateWorkflowOutput.RevisionId + - CreateWorkflowOutput.IsLatestVersion + - UpdateWorkflowOutput.Warnings + # The user supplies the workflow definition out-of-band via + # DefinitionS3Location; it can be arbitrarily large. Reflecting it back into + # status would bloat the CR in etcd for no benefit, so drop it from the read. + - GetWorkflowOutput.WorkflowDefinition +resources: + Workflow: + is_arn_primary_key: true + ignore_idempotency_token: true + exceptions: + errors: + 404: + code: ResourceNotFoundException + terminal_codes: + - ValidationException + fields: + Name: + is_immutable: true + EncryptionConfiguration: + is_immutable: true + EncryptionConfiguration.Type: + go_tag: json:"type,omitempty" + WorkflowStatus: + go_tag: json:"status,omitempty" + WorkflowVersion: + go_tag: json:"version,omitempty" + # GetWorkflow server-defaults these when the user omits them (TriggerMode + # -> "manual_only", LoggingConfiguration -> a synthesized log group), so + # let the runtime late-initialize the spec from the read instead of + # masking the phantom delta in sdkFind. skip_incomplete_check avoids + # holding the resource unsynced while it settles. + TriggerMode: + late_initialize: + skip_incomplete_check: {} + LoggingConfiguration: + late_initialize: + skip_incomplete_check: {} + ScheduleConfiguration: + is_read_only: true + from: + operation: GetWorkflow + path: ScheduleConfiguration + ModifiedAt: + is_read_only: true + from: + operation: GetWorkflow + path: ModifiedAt + RoleArn: + references: + resource: Role + service_name: iam + path: Status.ACKResourceMetadata.ARN + DefinitionS3Location.Bucket: + references: + resource: Bucket + service_name: s3 + path: Spec.Name + EncryptionConfiguration.KmsKeyId: + references: + resource: Key + service_name: kms + path: Status.ACKResourceMetadata.ARN + NetworkConfiguration.SecurityGroupIds: + references: + resource: SecurityGroup + service_name: ec2 + path: Status.ID + NetworkConfiguration.SubnetIds: + references: + resource: Subnet + service_name: ec2 + path: Status.SubnetID + hooks: + sdk_read_one_post_set_output: + template_path: hooks/workflow/sdk_read_one_post_set_output.go.tpl + sdk_update_pre_build_request: + template_path: hooks/workflow/sdk_update_pre_build_request.go.tpl + sdk_delete_post_build_request: + template_path: hooks/workflow/sdk_delete_post_build_request.go.tpl + synced: + when: + - path: Status.WorkflowStatus + in: + - READY + print: + add_age_column: true + add_synced_column: true + additional_columns: + - name: STATUS + json_path: .status.status + type: string + index: 10 + - name: VERSION + json_path: .status.version + type: string + index: 20 diff --git a/apis/v1alpha1/types.go b/apis/v1alpha1/types.go index 9b23f5e..0d8d215 100644 --- a/apis/v1alpha1/types.go +++ b/apis/v1alpha1/types.go @@ -27,3 +27,159 @@ var ( _ = &aws.JSONValue{} _ = ackv1alpha1.AWSAccountID("") ) + +// Specifies the Amazon S3 location of a workflow definition file. This structure +// contains the bucket name, object key, and optional version ID for the workflow +// definition. Amazon Managed Workflows for Apache Airflow Serverless takes +// a snapshot of the definition file at the time of workflow creation or update, +// ensuring that the workflow behavior remains consistent even if the source +// file is modified. The definition must be a valid YAML file that uses supported +// Amazon Web Services operators and Amazon Managed Workflows for Apache Airflow +// Serverless syntax. +type DefinitionS3Location struct { + Bucket *string `json:"bucket,omitempty"` + // Reference field for Bucket + BucketRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"bucketRef,omitempty"` + ObjectKey *string `json:"objectKey,omitempty"` + VersionID *string `json:"versionID,omitempty"` +} + +// Configuration for encrypting workflow data at rest and in transit. Amazon +// Managed Workflows for Apache Airflow Serverless provides comprehensive encryption +// capabilities to protect sensitive workflow data, parameters, and execution +// logs. When using customer-managed keys, the service integrates with Amazon +// Web Services KMS to provide fine-grained access control and audit capabilities. +// Encryption is applied consistently across the distributed execution environment +// including task containers, metadata storage, and log streams. +type EncryptionConfiguration struct { + KMSKeyID *string `json:"kmsKeyID,omitempty"` + // Reference field for KMSKeyID + KMSKeyRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"kmsKeyRef,omitempty"` + Type *string `json:"type,omitempty"` +} + +// Configuration for workflow logging that specifies where you should store +// your workflow execution logs. Amazon Managed Workflows for Apache Airflow +// Serverless provides comprehensive logging capabilities that capture workflow +// execution details, task-level information, and system events. Logs are automatically +// exported to your specified CloudWatch log group using remote logging functionality, +// providing centralized observability across the distributed, multi-tenant +// execution environment. This enables effective debugging, monitoring, and +// compliance auditing of workflow executions. +type LoggingConfiguration struct { + LogGroupName *string `json:"logGroupName,omitempty"` +} + +// Network configuration for workflow execution. Specifies VPC security groups +// and subnets for secure network access. When provided, Amazon Managed Workflows +// for Apache Airflow Serverless deploys ECS worker tasks in your specified +// VPC configuration, enabling secure access to VPC-only resources. The service +// uses a proxy API container architecture where one container handles external +// communication while the worker container connects to your VPC for task execution. +// This design provides both security isolation and connectivity flexibility. +type NetworkConfiguration struct { + SecurityGroupIDs []*string `json:"securityGroupIDs,omitempty"` + // Reference field for SecurityGroupIDs + SecurityGroupRefs []*ackv1alpha1.AWSResourceReferenceWrapper `json:"securityGroupRefs,omitempty"` + SubnetIDs []*string `json:"subnetIDs,omitempty"` + // Reference field for SubnetIDs + SubnetRefs []*ackv1alpha1.AWSResourceReferenceWrapper `json:"subnetRefs,omitempty"` +} + +// Summary information about a workflow run's execution details, including status +// and timing information. +type RunDetailSummary struct { + CreatedOn *metav1.Time `json:"createdOn,omitempty"` + EndedAt *metav1.Time `json:"endedAt,omitempty"` + StartedAt *metav1.Time `json:"startedAt,omitempty"` +} + +// The configuration to use to schedule automated workflow execution using cron +// expressions. Amazon Managed Workflows for Apache Airflow Serverless integrates +// with EventBridge Scheduler to provide cost-effective, timezone-aware scheduling +// capabilities. The service supports both time-based and event-based scheduling +// (event-based scheduling available post-GA). When a workflow definition includes +// scheduling information, Amazon Managed Workflows for Apache Airflow Serverless +// automatically configures the appropriate triggers and ensures only one version +// of a workflow has an active schedule at any time. +type ScheduleConfiguration struct { + CronExpression *string `json:"cronExpression,omitempty"` +} + +// Summary information about a task instance within a workflow run, including +// its status and execution details. +type TaskInstanceSummary struct { + DurationInSeconds *int64 `json:"durationInSeconds,omitempty"` + OperatorName *string `json:"operatorName,omitempty"` + WorkflowARN *string `json:"workflowARN,omitempty"` +} + +// Contains information about a field that failed validation, including the +// field name and a descriptive error message. +type ValidationExceptionField struct { + Name *string `json:"name,omitempty"` +} + +// Detailed information about a workflow run execution, including timing, status, +// error information, and associated task instances. This structure provides +// comprehensive visibility into the workflow execution lifecycle within the +// Amazon Managed Workflows for Apache Airflow Serverless serverless environment. +// The service tracks execution across distributed ECS worker tasks and provides +// detailed timing information, error diagnostics, and task instance relationships +// to support effective monitoring and troubleshooting of complex workflow executions. +type WorkflowRunDetail struct { + CompletedOn *metav1.Time `json:"completedOn,omitempty"` + CreatedAt *metav1.Time `json:"createdAt,omitempty"` + Duration *int64 `json:"duration,omitempty"` + ErrorMessage *string `json:"errorMessage,omitempty"` + ModifiedAt *metav1.Time `json:"modifiedAt,omitempty"` + StartedOn *metav1.Time `json:"startedOn,omitempty"` + WorkflowARN *string `json:"workflowARN,omitempty"` +} + +// Summary information about a workflow run, including basic identification +// and status information. +type WorkflowRunSummary struct { + WorkflowARN *string `json:"workflowARN,omitempty"` +} + +// Summary information about a workflow, including basic identification and +// metadata. +type WorkflowSummary struct { + CreatedAt *metav1.Time `json:"createdAt,omitempty"` + Description *string `json:"description,omitempty"` + ModifiedAt *metav1.Time `json:"modifiedAt,omitempty"` + Name *string `json:"name,omitempty"` + TriggerMode *string `json:"triggerMode,omitempty"` + WorkflowARN *string `json:"workflowARN,omitempty"` + WorkflowStatus *string `json:"workflowStatus,omitempty"` + WorkflowVersion *string `json:"workflowVersion,omitempty"` +} + +// Summary information about a workflow version, including identification and +// configuration details. +type WorkflowVersionSummary struct { + CreatedAt *metav1.Time `json:"createdAt,omitempty"` + // Specifies the Amazon S3 location of a workflow definition file. This structure + // contains the bucket name, object key, and optional version ID for the workflow + // definition. Amazon Managed Workflows for Apache Airflow Serverless takes + // a snapshot of the definition file at the time of workflow creation or update, + // ensuring that the workflow behavior remains consistent even if the source + // file is modified. The definition must be a valid YAML file that uses supported + // Amazon Web Services operators and Amazon Managed Workflows for Apache Airflow + // Serverless syntax. + DefinitionS3Location *DefinitionS3Location `json:"definitionS3Location,omitempty"` + ModifiedAt *metav1.Time `json:"modifiedAt,omitempty"` + // The configuration to use to schedule automated workflow execution using cron + // expressions. Amazon Managed Workflows for Apache Airflow Serverless integrates + // with EventBridge Scheduler to provide cost-effective, timezone-aware scheduling + // capabilities. The service supports both time-based and event-based scheduling + // (event-based scheduling available post-GA). When a workflow definition includes + // scheduling information, Amazon Managed Workflows for Apache Airflow Serverless + // automatically configures the appropriate triggers and ensures only one version + // of a workflow has an active schedule at any time. + ScheduleConfiguration *ScheduleConfiguration `json:"scheduleConfiguration,omitempty"` + TriggerMode *string `json:"triggerMode,omitempty"` + WorkflowARN *string `json:"workflowARN,omitempty"` + WorkflowVersion *string `json:"workflowVersion,omitempty"` +} diff --git a/apis/v1alpha1/workflow.go b/apis/v1alpha1/workflow.go new file mode 100644 index 0000000..c5ae2c2 --- /dev/null +++ b/apis/v1alpha1/workflow.go @@ -0,0 +1,166 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package v1alpha1 + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// WorkflowSpec defines the desired state of Workflow. +type WorkflowSpec struct { + + // The Amazon S3 location where the workflow definition file is stored. This + // must point to a valid YAML file that defines the workflow structure using + // supported Amazon Web Services operators and tasks. Amazon Managed Workflows + // for Apache Airflow Serverless takes a snapshot of the definition at creation + // time, so subsequent changes to the Amazon S3 object will not affect the workflow + // unless you create a new version. In your YAML definition, include task dependencies, + // scheduling information, and operator configurations that are compatible with + // the Amazon Managed Workflows for Apache Airflow Serverless execution environment. + // +kubebuilder:validation:Required + DefinitionS3Location *DefinitionS3Location `json:"definitionS3Location"` + // An optional description of the workflow that you can use to provide additional + // context about the workflow's purpose and functionality. + // + // Regex Pattern: `^.+$` + Description *string `json:"description,omitempty"` + // The configuration for encrypting workflow data at rest and in transit. Specifies + // the encryption type and optional KMS key for customer-managed encryption. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable once set" + EncryptionConfiguration *EncryptionConfiguration `json:"encryptionConfiguration,omitempty"` + // The version of the Amazon Managed Workflows for Apache Airflow Serverless + // engine that you want to use for this workflow. This determines the feature + // set, supported operators, and execution environment capabilities available + // to your workflow. Amazon Managed Workflows for Apache Airflow Serverless + // maintains backward compatibility across versions while introducing new features + // and improvements. Currently supports version 1 with plans for additional + // versions as the service evolves. + EngineVersion *int64 `json:"engineVersion,omitempty"` + // The configuration for workflow logging. Specifies the CloudWatch log group + // where workflow execution logs are stored. Amazon Managed Workflows for Apache + // Airflow Serverless automatically exports worker logs and task-level information + // to the specified log group in your account using remote logging functionality. + // This provides comprehensive observability for debugging and monitoring workflow + // execution across the distributed, serverless environment. + LoggingConfiguration *LoggingConfiguration `json:"loggingConfiguration,omitempty"` + // The name of the workflow. You must use unique workflow names within your + // Amazon Web Services account. The service generates a unique identifier that + // is appended to ensure temporal uniqueness across the account lifecycle. + // + // Regex Pattern: `^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*$` + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable once set" + // +kubebuilder:validation:Required + Name *string `json:"name"` + // Network configuration for the workflow execution environment, including VPC + // security groups and subnets for secure network access. When specified, Amazon + // Managed Workflows for Apache Airflow Serverless deploys ECS worker tasks + // in your customer VPC to provide secure connectivity to your resources. If + // not specified, tasks run in the service's default worker VPC with network + // isolation from other customers. This configuration enables secure access + // to VPC-only resources like RDS databases or private endpoints. + NetworkConfiguration *NetworkConfiguration `json:"networkConfiguration,omitempty"` + // The Amazon Resource Name (ARN) of the IAM role that Amazon Managed Workflows + // for Apache Airflow Serverless assumes when executing the workflow. This role + // must have the necessary permissions to access the required Amazon Web Services + // services and resources that your workflow tasks will interact with. The role + // is used for task execution in the isolated, multi-tenant environment and + // should follow the principle of least privilege. Amazon Managed Workflows + // for Apache Airflow Serverless validates role access during workflow creation + // but runtime permission checks are performed by the target services. + // + // Regex Pattern: `^arn:aws(?:-(?:cn|us-gov|iso|iso-b|iso-e|iso-f))?:iam::[0-9]{12}:role(/[a-zA-Z0-9+=,.@_\-]{1,512})*?/[a-zA-Z0-9+=,.@_\-]{1,64}$` + RoleARN *string `json:"roleARN,omitempty"` + RoleRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"roleRef,omitempty"` + // A map of tags to assign to the workflow resource. Tags are key-value pairs + // that are used for resource organization and cost allocation. + Tags map[string]*string `json:"tags,omitempty"` + // The trigger mode for the workflow execution. + // + // Regex Pattern: `^.*$` + TriggerMode *string `json:"triggerMode,omitempty"` +} + +// WorkflowStatus defines the observed state of Workflow +type WorkflowStatus struct { + // All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + // that is used to contain resource sync state, account ownership, + // constructed ARN for the resource + // +kubebuilder:validation:Optional + ACKResourceMetadata *ackv1alpha1.ResourceMetadata `json:"ackResourceMetadata"` + // All CRs managed by ACK have a common `Status.Conditions` member that + // contains a collection of `ackv1alpha1.Condition` objects that describe + // the various terminal states of the CR and its backend AWS service API + // resource + // +kubebuilder:validation:Optional + Conditions []*ackv1alpha1.Condition `json:"conditions"` + // The timestamp when the workflow was created, in ISO 8601 date-time format. + // +kubebuilder:validation:Optional + CreatedAt *metav1.Time `json:"createdAt,omitempty"` + // The timestamp when the workflow was last modified, in ISO 8601 date-time + // format. + // +kubebuilder:validation:Optional + ModifiedAt *metav1.Time `json:"modifiedAt,omitempty"` + // The schedule configuration for the workflow, including cron expressions for + // automated execution. Amazon Managed Workflows for Apache Airflow Serverless + // uses EventBridge Scheduler for cost-effective, timezone-aware scheduling. + // When a workflow includes schedule information in its YAML definition, the + // service automatically configures the appropriate triggers for automated execution. + // Only one version of a workflow can have an active schedule at any given time. + // +kubebuilder:validation:Optional + ScheduleConfiguration *ScheduleConfiguration `json:"scheduleConfiguration,omitempty"` + // The current status of the workflow. Possible values are READY (workflow is + // ready to run) and DELETING (workflow is being deleted). + // +kubebuilder:validation:Optional + WorkflowStatus *string `json:"status,omitempty"` + // The version identifier of the workflow. This is a service-generated alphanumeric + // string that uniquely identifies this version of the workflow. Amazon Managed + // Workflows for Apache Airflow Serverless uses a version-first approach where + // each workflow can have multiple immutable versions, which allows you to maintain + // different configurations and roll back to previous versions as needed. The + // version identifier is used in ARNs and API operations to reference specific + // workflow versions. + // + // Regex Pattern: `^[a-z0-9]{32}$` + // +kubebuilder:validation:Optional + WorkflowVersion *string `json:"version,omitempty"` +} + +// Workflow is the Schema for the Workflows API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="STATUS",type=string,priority=0,JSONPath=`.status.status` +// +kubebuilder:printcolumn:name="VERSION",type=string,priority=0,JSONPath=`.status.version` +// +kubebuilder:printcolumn:name="Synced",type="string",priority=0,JSONPath=".status.conditions[?(@.type==\"ACK.ResourceSynced\")].status" +// +kubebuilder:printcolumn:name="Age",type="date",priority=0,JSONPath=".metadata.creationTimestamp" +type Workflow struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec WorkflowSpec `json:"spec,omitempty"` + Status WorkflowStatus `json:"status,omitempty"` +} + +// WorkflowList contains a list of Workflow +// +kubebuilder:object:root=true +type WorkflowList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Workflow `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Workflow{}, &WorkflowList{}) +} diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000..8bc722e --- /dev/null +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,627 @@ +//go:build !ignore_autogenerated + +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + corev1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DefinitionS3Location) DeepCopyInto(out *DefinitionS3Location) { + *out = *in + if in.Bucket != nil { + in, out := &in.Bucket, &out.Bucket + *out = new(string) + **out = **in + } + if in.BucketRef != nil { + in, out := &in.BucketRef, &out.BucketRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + if in.ObjectKey != nil { + in, out := &in.ObjectKey, &out.ObjectKey + *out = new(string) + **out = **in + } + if in.VersionID != nil { + in, out := &in.VersionID, &out.VersionID + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefinitionS3Location. +func (in *DefinitionS3Location) DeepCopy() *DefinitionS3Location { + if in == nil { + return nil + } + out := new(DefinitionS3Location) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EncryptionConfiguration) DeepCopyInto(out *EncryptionConfiguration) { + *out = *in + if in.KMSKeyID != nil { + in, out := &in.KMSKeyID, &out.KMSKeyID + *out = new(string) + **out = **in + } + if in.KMSKeyRef != nil { + in, out := &in.KMSKeyRef, &out.KMSKeyRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + if in.Type != nil { + in, out := &in.Type, &out.Type + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EncryptionConfiguration. +func (in *EncryptionConfiguration) DeepCopy() *EncryptionConfiguration { + if in == nil { + return nil + } + out := new(EncryptionConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LoggingConfiguration) DeepCopyInto(out *LoggingConfiguration) { + *out = *in + if in.LogGroupName != nil { + in, out := &in.LogGroupName, &out.LogGroupName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggingConfiguration. +func (in *LoggingConfiguration) DeepCopy() *LoggingConfiguration { + if in == nil { + return nil + } + out := new(LoggingConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkConfiguration) DeepCopyInto(out *NetworkConfiguration) { + *out = *in + if in.SecurityGroupIDs != nil { + in, out := &in.SecurityGroupIDs, &out.SecurityGroupIDs + *out = make([]*string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(string) + **out = **in + } + } + } + if in.SecurityGroupRefs != nil { + in, out := &in.SecurityGroupRefs, &out.SecurityGroupRefs + *out = make([]*corev1alpha1.AWSResourceReferenceWrapper, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + } + } + if in.SubnetIDs != nil { + in, out := &in.SubnetIDs, &out.SubnetIDs + *out = make([]*string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(string) + **out = **in + } + } + } + if in.SubnetRefs != nil { + in, out := &in.SubnetRefs, &out.SubnetRefs + *out = make([]*corev1alpha1.AWSResourceReferenceWrapper, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkConfiguration. +func (in *NetworkConfiguration) DeepCopy() *NetworkConfiguration { + if in == nil { + return nil + } + out := new(NetworkConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RunDetailSummary) DeepCopyInto(out *RunDetailSummary) { + *out = *in + if in.CreatedOn != nil { + in, out := &in.CreatedOn, &out.CreatedOn + *out = (*in).DeepCopy() + } + if in.EndedAt != nil { + in, out := &in.EndedAt, &out.EndedAt + *out = (*in).DeepCopy() + } + if in.StartedAt != nil { + in, out := &in.StartedAt, &out.StartedAt + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunDetailSummary. +func (in *RunDetailSummary) DeepCopy() *RunDetailSummary { + if in == nil { + return nil + } + out := new(RunDetailSummary) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScheduleConfiguration) DeepCopyInto(out *ScheduleConfiguration) { + *out = *in + if in.CronExpression != nil { + in, out := &in.CronExpression, &out.CronExpression + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScheduleConfiguration. +func (in *ScheduleConfiguration) DeepCopy() *ScheduleConfiguration { + if in == nil { + return nil + } + out := new(ScheduleConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskInstanceSummary) DeepCopyInto(out *TaskInstanceSummary) { + *out = *in + if in.DurationInSeconds != nil { + in, out := &in.DurationInSeconds, &out.DurationInSeconds + *out = new(int64) + **out = **in + } + if in.OperatorName != nil { + in, out := &in.OperatorName, &out.OperatorName + *out = new(string) + **out = **in + } + if in.WorkflowARN != nil { + in, out := &in.WorkflowARN, &out.WorkflowARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskInstanceSummary. +func (in *TaskInstanceSummary) DeepCopy() *TaskInstanceSummary { + if in == nil { + return nil + } + out := new(TaskInstanceSummary) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidationExceptionField) DeepCopyInto(out *ValidationExceptionField) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationExceptionField. +func (in *ValidationExceptionField) DeepCopy() *ValidationExceptionField { + if in == nil { + return nil + } + out := new(ValidationExceptionField) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Workflow) DeepCopyInto(out *Workflow) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Workflow. +func (in *Workflow) DeepCopy() *Workflow { + if in == nil { + return nil + } + out := new(Workflow) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Workflow) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkflowList) DeepCopyInto(out *WorkflowList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Workflow, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowList. +func (in *WorkflowList) DeepCopy() *WorkflowList { + if in == nil { + return nil + } + out := new(WorkflowList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WorkflowList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkflowRunDetail) DeepCopyInto(out *WorkflowRunDetail) { + *out = *in + if in.CompletedOn != nil { + in, out := &in.CompletedOn, &out.CompletedOn + *out = (*in).DeepCopy() + } + if in.CreatedAt != nil { + in, out := &in.CreatedAt, &out.CreatedAt + *out = (*in).DeepCopy() + } + if in.Duration != nil { + in, out := &in.Duration, &out.Duration + *out = new(int64) + **out = **in + } + if in.ErrorMessage != nil { + in, out := &in.ErrorMessage, &out.ErrorMessage + *out = new(string) + **out = **in + } + if in.ModifiedAt != nil { + in, out := &in.ModifiedAt, &out.ModifiedAt + *out = (*in).DeepCopy() + } + if in.StartedOn != nil { + in, out := &in.StartedOn, &out.StartedOn + *out = (*in).DeepCopy() + } + if in.WorkflowARN != nil { + in, out := &in.WorkflowARN, &out.WorkflowARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowRunDetail. +func (in *WorkflowRunDetail) DeepCopy() *WorkflowRunDetail { + if in == nil { + return nil + } + out := new(WorkflowRunDetail) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkflowRunSummary) DeepCopyInto(out *WorkflowRunSummary) { + *out = *in + if in.WorkflowARN != nil { + in, out := &in.WorkflowARN, &out.WorkflowARN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowRunSummary. +func (in *WorkflowRunSummary) DeepCopy() *WorkflowRunSummary { + if in == nil { + return nil + } + out := new(WorkflowRunSummary) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkflowSpec) DeepCopyInto(out *WorkflowSpec) { + *out = *in + if in.DefinitionS3Location != nil { + in, out := &in.DefinitionS3Location, &out.DefinitionS3Location + *out = new(DefinitionS3Location) + (*in).DeepCopyInto(*out) + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.EncryptionConfiguration != nil { + in, out := &in.EncryptionConfiguration, &out.EncryptionConfiguration + *out = new(EncryptionConfiguration) + (*in).DeepCopyInto(*out) + } + if in.EngineVersion != nil { + in, out := &in.EngineVersion, &out.EngineVersion + *out = new(int64) + **out = **in + } + if in.LoggingConfiguration != nil { + in, out := &in.LoggingConfiguration, &out.LoggingConfiguration + *out = new(LoggingConfiguration) + (*in).DeepCopyInto(*out) + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.NetworkConfiguration != nil { + in, out := &in.NetworkConfiguration, &out.NetworkConfiguration + *out = new(NetworkConfiguration) + (*in).DeepCopyInto(*out) + } + if in.RoleARN != nil { + in, out := &in.RoleARN, &out.RoleARN + *out = new(string) + **out = **in + } + if in.RoleRef != nil { + in, out := &in.RoleRef, &out.RoleRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make(map[string]*string, len(*in)) + for key, val := range *in { + var outVal *string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = new(string) + **out = **in + } + (*out)[key] = outVal + } + } + if in.TriggerMode != nil { + in, out := &in.TriggerMode, &out.TriggerMode + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowSpec. +func (in *WorkflowSpec) DeepCopy() *WorkflowSpec { + if in == nil { + return nil + } + out := new(WorkflowSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkflowStatus) DeepCopyInto(out *WorkflowStatus) { + *out = *in + if in.ACKResourceMetadata != nil { + in, out := &in.ACKResourceMetadata, &out.ACKResourceMetadata + *out = new(corev1alpha1.ResourceMetadata) + (*in).DeepCopyInto(*out) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]*corev1alpha1.Condition, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(corev1alpha1.Condition) + (*in).DeepCopyInto(*out) + } + } + } + if in.CreatedAt != nil { + in, out := &in.CreatedAt, &out.CreatedAt + *out = (*in).DeepCopy() + } + if in.ModifiedAt != nil { + in, out := &in.ModifiedAt, &out.ModifiedAt + *out = (*in).DeepCopy() + } + if in.ScheduleConfiguration != nil { + in, out := &in.ScheduleConfiguration, &out.ScheduleConfiguration + *out = new(ScheduleConfiguration) + (*in).DeepCopyInto(*out) + } + if in.WorkflowStatus != nil { + in, out := &in.WorkflowStatus, &out.WorkflowStatus + *out = new(string) + **out = **in + } + if in.WorkflowVersion != nil { + in, out := &in.WorkflowVersion, &out.WorkflowVersion + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowStatus. +func (in *WorkflowStatus) DeepCopy() *WorkflowStatus { + if in == nil { + return nil + } + out := new(WorkflowStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkflowSummary) DeepCopyInto(out *WorkflowSummary) { + *out = *in + if in.CreatedAt != nil { + in, out := &in.CreatedAt, &out.CreatedAt + *out = (*in).DeepCopy() + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.ModifiedAt != nil { + in, out := &in.ModifiedAt, &out.ModifiedAt + *out = (*in).DeepCopy() + } + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.TriggerMode != nil { + in, out := &in.TriggerMode, &out.TriggerMode + *out = new(string) + **out = **in + } + if in.WorkflowARN != nil { + in, out := &in.WorkflowARN, &out.WorkflowARN + *out = new(string) + **out = **in + } + if in.WorkflowStatus != nil { + in, out := &in.WorkflowStatus, &out.WorkflowStatus + *out = new(string) + **out = **in + } + if in.WorkflowVersion != nil { + in, out := &in.WorkflowVersion, &out.WorkflowVersion + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowSummary. +func (in *WorkflowSummary) DeepCopy() *WorkflowSummary { + if in == nil { + return nil + } + out := new(WorkflowSummary) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkflowVersionSummary) DeepCopyInto(out *WorkflowVersionSummary) { + *out = *in + if in.CreatedAt != nil { + in, out := &in.CreatedAt, &out.CreatedAt + *out = (*in).DeepCopy() + } + if in.DefinitionS3Location != nil { + in, out := &in.DefinitionS3Location, &out.DefinitionS3Location + *out = new(DefinitionS3Location) + (*in).DeepCopyInto(*out) + } + if in.ModifiedAt != nil { + in, out := &in.ModifiedAt, &out.ModifiedAt + *out = (*in).DeepCopy() + } + if in.ScheduleConfiguration != nil { + in, out := &in.ScheduleConfiguration, &out.ScheduleConfiguration + *out = new(ScheduleConfiguration) + (*in).DeepCopyInto(*out) + } + if in.TriggerMode != nil { + in, out := &in.TriggerMode, &out.TriggerMode + *out = new(string) + **out = **in + } + if in.WorkflowARN != nil { + in, out := &in.WorkflowARN, &out.WorkflowARN + *out = new(string) + **out = **in + } + if in.WorkflowVersion != nil { + in, out := &in.WorkflowVersion, &out.WorkflowVersion + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowVersionSummary. +func (in *WorkflowVersionSummary) DeepCopy() *WorkflowVersionSummary { + if in == nil { + return nil + } + out := new(WorkflowVersionSummary) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/controller/main.go b/cmd/controller/main.go index a854da8..32fc9a3 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -19,12 +19,16 @@ import ( "context" "os" + ec2apitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" + iamapitypes "github.com/aws-controllers-k8s/iam-controller/apis/v1alpha1" + kmsapitypes "github.com/aws-controllers-k8s/kms-controller/apis/v1alpha1" ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" ackrt "github.com/aws-controllers-k8s/runtime/pkg/runtime" acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" ackrtutil "github.com/aws-controllers-k8s/runtime/pkg/util" ackrtwebhook "github.com/aws-controllers-k8s/runtime/pkg/webhook" + s3apitypes "github.com/aws-controllers-k8s/s3-controller/apis/v1alpha1" flag "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -39,6 +43,8 @@ import ( svctypes "github.com/aws-controllers-k8s/mwaaserverless-controller/apis/v1alpha1" svcresource "github.com/aws-controllers-k8s/mwaaserverless-controller/pkg/resource" + _ "github.com/aws-controllers-k8s/mwaaserverless-controller/pkg/resource/workflow" + "github.com/aws-controllers-k8s/mwaaserverless-controller/pkg/version" ) @@ -54,6 +60,10 @@ func init() { _ = svctypes.AddToScheme(scheme) _ = ackv1alpha1.AddToScheme(scheme) + _ = ec2apitypes.AddToScheme(scheme) + _ = iamapitypes.AddToScheme(scheme) + _ = kmsapitypes.AddToScheme(scheme) + _ = s3apitypes.AddToScheme(scheme) } func main() { diff --git a/config/crd/bases/mwaaserverless.services.k8s.aws_workflows.yaml b/config/crd/bases/mwaaserverless.services.k8s.aws_workflows.yaml new file mode 100644 index 0000000..8a7aa08 --- /dev/null +++ b/config/crd/bases/mwaaserverless.services.k8s.aws_workflows.yaml @@ -0,0 +1,377 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: workflows.mwaaserverless.services.k8s.aws +spec: + group: mwaaserverless.services.k8s.aws + names: + kind: Workflow + listKind: WorkflowList + plural: workflows + singular: workflow + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.status + name: STATUS + type: string + - jsonPath: .status.version + name: VERSION + type: string + - jsonPath: .status.conditions[?(@.type=="ACK.ResourceSynced")].status + name: Synced + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Workflow is the Schema for the Workflows API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: WorkflowSpec defines the desired state of Workflow. + properties: + definitionS3Location: + description: |- + The Amazon S3 location where the workflow definition file is stored. This + must point to a valid YAML file that defines the workflow structure using + supported Amazon Web Services operators and tasks. Amazon Managed Workflows + for Apache Airflow Serverless takes a snapshot of the definition at creation + time, so subsequent changes to the Amazon S3 object will not affect the workflow + unless you create a new version. In your YAML definition, include task dependencies, + scheduling information, and operator configurations that are compatible with + the Amazon Managed Workflows for Apache Airflow Serverless execution environment. + properties: + bucket: + type: string + bucketRef: + description: Reference field for Bucket + properties: + from: + description: |- + AWSResourceReference provides all the values necessary to reference another + k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + namespace: + type: string + type: object + type: object + objectKey: + type: string + versionID: + type: string + type: object + description: + description: |- + An optional description of the workflow that you can use to provide additional + context about the workflow's purpose and functionality. + + Regex Pattern: `^.+$` + type: string + encryptionConfiguration: + description: |- + The configuration for encrypting workflow data at rest and in transit. Specifies + the encryption type and optional KMS key for customer-managed encryption. + properties: + kmsKeyID: + type: string + kmsKeyRef: + description: Reference field for KMSKeyID + properties: + from: + description: |- + AWSResourceReference provides all the values necessary to reference another + k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + namespace: + type: string + type: object + type: object + type: + type: string + type: object + x-kubernetes-validations: + - message: Value is immutable once set + rule: self == oldSelf + engineVersion: + description: |- + The version of the Amazon Managed Workflows for Apache Airflow Serverless + engine that you want to use for this workflow. This determines the feature + set, supported operators, and execution environment capabilities available + to your workflow. Amazon Managed Workflows for Apache Airflow Serverless + maintains backward compatibility across versions while introducing new features + and improvements. Currently supports version 1 with plans for additional + versions as the service evolves. + format: int64 + type: integer + loggingConfiguration: + description: |- + The configuration for workflow logging. Specifies the CloudWatch log group + where workflow execution logs are stored. Amazon Managed Workflows for Apache + Airflow Serverless automatically exports worker logs and task-level information + to the specified log group in your account using remote logging functionality. + This provides comprehensive observability for debugging and monitoring workflow + execution across the distributed, serverless environment. + properties: + logGroupName: + type: string + type: object + name: + description: |- + The name of the workflow. You must use unique workflow names within your + Amazon Web Services account. The service generates a unique identifier that + is appended to ensure temporal uniqueness across the account lifecycle. + + Regex Pattern: `^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*$` + type: string + x-kubernetes-validations: + - message: Value is immutable once set + rule: self == oldSelf + networkConfiguration: + description: |- + Network configuration for the workflow execution environment, including VPC + security groups and subnets for secure network access. When specified, Amazon + Managed Workflows for Apache Airflow Serverless deploys ECS worker tasks + in your customer VPC to provide secure connectivity to your resources. If + not specified, tasks run in the service's default worker VPC with network + isolation from other customers. This configuration enables secure access + to VPC-only resources like RDS databases or private endpoints. + properties: + securityGroupIDs: + items: + type: string + type: array + securityGroupRefs: + description: Reference field for SecurityGroupIDs + items: + description: "AWSResourceReferenceWrapper provides a wrapper + around *AWSResourceReference\ntype to provide more user friendly + syntax for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ name: my-api" + properties: + from: + description: |- + AWSResourceReference provides all the values necessary to reference another + k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + namespace: + type: string + type: object + type: object + type: array + subnetIDs: + items: + type: string + type: array + subnetRefs: + description: Reference field for SubnetIDs + items: + description: "AWSResourceReferenceWrapper provides a wrapper + around *AWSResourceReference\ntype to provide more user friendly + syntax for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ name: my-api" + properties: + from: + description: |- + AWSResourceReference provides all the values necessary to reference another + k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + namespace: + type: string + type: object + type: object + type: array + type: object + roleARN: + description: |- + The Amazon Resource Name (ARN) of the IAM role that Amazon Managed Workflows + for Apache Airflow Serverless assumes when executing the workflow. This role + must have the necessary permissions to access the required Amazon Web Services + services and resources that your workflow tasks will interact with. The role + is used for task execution in the isolated, multi-tenant environment and + should follow the principle of least privilege. Amazon Managed Workflows + for Apache Airflow Serverless validates role access during workflow creation + but runtime permission checks are performed by the target services. + + Regex Pattern: `^arn:aws(?:-(?:cn|us-gov|iso|iso-b|iso-e|iso-f))?:iam::[0-9]{12}:role(/[a-zA-Z0-9+=,.@_\-]{1,512})*?/[a-zA-Z0-9+=,.@_\-]{1,64}$` + type: string + roleRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference\ntype to provide more user friendly syntax + for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ name: my-api" + properties: + from: + description: |- + AWSResourceReference provides all the values necessary to reference another + k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + namespace: + type: string + type: object + type: object + tags: + additionalProperties: + type: string + description: |- + A map of tags to assign to the workflow resource. Tags are key-value pairs + that are used for resource organization and cost allocation. + type: object + triggerMode: + description: |- + The trigger mode for the workflow execution. + + Regex Pattern: `^.*$` + type: string + required: + - definitionS3Location + - name + type: object + status: + description: WorkflowStatus defines the observed state of Workflow + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + partition: + description: Partition is the AWS partition in which the resource + exists or will exist + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRs managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + createdAt: + description: The timestamp when the workflow was created, in ISO 8601 + date-time format. + format: date-time + type: string + modifiedAt: + description: |- + The timestamp when the workflow was last modified, in ISO 8601 date-time + format. + format: date-time + type: string + scheduleConfiguration: + description: |- + The schedule configuration for the workflow, including cron expressions for + automated execution. Amazon Managed Workflows for Apache Airflow Serverless + uses EventBridge Scheduler for cost-effective, timezone-aware scheduling. + When a workflow includes schedule information in its YAML definition, the + service automatically configures the appropriate triggers for automated execution. + Only one version of a workflow can have an active schedule at any given time. + properties: + cronExpression: + type: string + type: object + status: + description: |- + The current status of the workflow. Possible values are READY (workflow is + ready to run) and DELETING (workflow is being deleted). + type: string + version: + description: |- + The version identifier of the workflow. This is a service-generated alphanumeric + string that uniquely identifies this version of the workflow. Amazon Managed + Workflows for Apache Airflow Serverless uses a version-first approach where + each workflow can have multiple immutable versions, which allows you to maintain + different configurations and roll back to previous versions as needed. The + version identifier is used in ARNs and API operations to reference specific + workflow versions. + + Regex Pattern: `^[a-z0-9]{32}$` + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index ea0b795..8e0c951 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -2,3 +2,4 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - common + - bases/mwaaserverless.services.k8s.aws_workflows.yaml diff --git a/config/rbac/cluster-role-controller.yaml b/config/rbac/cluster-role-controller.yaml index febd02b..b648df7 100644 --- a/config/rbac/cluster-role-controller.yaml +++ b/config/rbac/cluster-role-controller.yaml @@ -22,6 +22,60 @@ rules: - get - list - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - securitygroups + - securitygroups/status + - subnets + - subnets/status + verbs: + - get + - list +- apiGroups: + - iam.services.k8s.aws + resources: + - roles + - roles/status + verbs: + - get + - list +- apiGroups: + - kms.services.k8s.aws + resources: + - keys + - keys/status + verbs: + - get + - list +- apiGroups: + - mwaaserverless.services.k8s.aws + resources: + - workflows + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mwaaserverless.services.k8s.aws + resources: + - workflows/status + verbs: + - get + - patch + - update +- apiGroups: + - s3.services.k8s.aws + resources: + - buckets + - buckets/status + verbs: + - get + - list - apiGroups: - services.k8s.aws resources: diff --git a/config/rbac/role-reader.yaml b/config/rbac/role-reader.yaml index e098b5f..13c6f17 100644 --- a/config/rbac/role-reader.yaml +++ b/config/rbac/role-reader.yaml @@ -9,6 +9,7 @@ rules: - apiGroups: - mwaaserverless.services.k8s.aws resources: + - workflows verbs: - get - list diff --git a/config/rbac/role-writer.yaml b/config/rbac/role-writer.yaml index 614fa8b..0bb3822 100644 --- a/config/rbac/role-writer.yaml +++ b/config/rbac/role-writer.yaml @@ -9,6 +9,7 @@ rules: - apiGroups: - mwaaserverless.services.k8s.aws resources: + - workflows verbs: - create - delete @@ -20,6 +21,7 @@ rules: - apiGroups: - mwaaserverless.services.k8s.aws resources: + - workflows verbs: - get - patch diff --git a/generator.yaml b/generator.yaml index f547e99..646169f 100644 --- a/generator.yaml +++ b/generator.yaml @@ -1,5 +1,106 @@ sdk_names: model_name: mwaa-serverless ignore: - resource_names: - - Workflow + field_paths: + # Warnings/RevisionId/IsLatestVersion are transient create/update response + # metadata, not part of the desired or observed resource state, so there is + # nothing for the controller to reconcile against. + - CreateWorkflowOutput.Warnings + - CreateWorkflowOutput.RevisionId + - CreateWorkflowOutput.IsLatestVersion + - UpdateWorkflowOutput.Warnings + # The user supplies the workflow definition out-of-band via + # DefinitionS3Location; it can be arbitrarily large. Reflecting it back into + # status would bloat the CR in etcd for no benefit, so drop it from the read. + - GetWorkflowOutput.WorkflowDefinition +resources: + Workflow: + is_arn_primary_key: true + ignore_idempotency_token: true + exceptions: + errors: + 404: + code: ResourceNotFoundException + terminal_codes: + - ValidationException + fields: + Name: + is_immutable: true + EncryptionConfiguration: + is_immutable: true + EncryptionConfiguration.Type: + go_tag: json:"type,omitempty" + WorkflowStatus: + go_tag: json:"status,omitempty" + WorkflowVersion: + go_tag: json:"version,omitempty" + # GetWorkflow server-defaults these when the user omits them (TriggerMode + # -> "manual_only", LoggingConfiguration -> a synthesized log group), so + # let the runtime late-initialize the spec from the read instead of + # masking the phantom delta in sdkFind. skip_incomplete_check avoids + # holding the resource unsynced while it settles. + TriggerMode: + late_initialize: + skip_incomplete_check: {} + LoggingConfiguration: + late_initialize: + skip_incomplete_check: {} + ScheduleConfiguration: + is_read_only: true + from: + operation: GetWorkflow + path: ScheduleConfiguration + ModifiedAt: + is_read_only: true + from: + operation: GetWorkflow + path: ModifiedAt + RoleArn: + references: + resource: Role + service_name: iam + path: Status.ACKResourceMetadata.ARN + DefinitionS3Location.Bucket: + references: + resource: Bucket + service_name: s3 + path: Spec.Name + EncryptionConfiguration.KmsKeyId: + references: + resource: Key + service_name: kms + path: Status.ACKResourceMetadata.ARN + NetworkConfiguration.SecurityGroupIds: + references: + resource: SecurityGroup + service_name: ec2 + path: Status.ID + NetworkConfiguration.SubnetIds: + references: + resource: Subnet + service_name: ec2 + path: Status.SubnetID + hooks: + sdk_read_one_post_set_output: + template_path: hooks/workflow/sdk_read_one_post_set_output.go.tpl + sdk_update_pre_build_request: + template_path: hooks/workflow/sdk_update_pre_build_request.go.tpl + sdk_delete_post_build_request: + template_path: hooks/workflow/sdk_delete_post_build_request.go.tpl + synced: + when: + - path: Status.WorkflowStatus + in: + - READY + print: + add_age_column: true + add_synced_column: true + additional_columns: + - name: STATUS + json_path: .status.status + type: string + index: 10 + - name: VERSION + json_path: .status.version + type: string + index: 20 diff --git a/go.mod b/go.mod index 2152c56..0d97980 100644 --- a/go.mod +++ b/go.mod @@ -3,28 +3,35 @@ module github.com/aws-controllers-k8s/mwaaserverless-controller go 1.25.0 require ( + github.com/aws-controllers-k8s/ec2-controller v1.18.2 + github.com/aws-controllers-k8s/iam-controller v1.7.3 + github.com/aws-controllers-k8s/kms-controller v1.3.3 github.com/aws-controllers-k8s/runtime v0.61.0 + github.com/aws-controllers-k8s/s3-controller v1.8.1 github.com/aws/aws-sdk-go v1.55.8 + github.com/aws/aws-sdk-go-v2 v1.42.1 + github.com/aws/aws-sdk-go-v2/service/mwaaserverless v1.2.1 + github.com/aws/smithy-go v1.27.3 + github.com/go-logr/logr v1.4.3 github.com/spf13/pflag v1.0.9 + k8s.io/api v0.35.0 k8s.io/apimachinery v0.35.0 k8s.io/client-go v0.35.0 sigs.k8s.io/controller-runtime v0.23.0 ) require ( - github.com/aws/aws-sdk-go-v2 v1.42.0 // indirect github.com/aws/aws-sdk-go-v2/config v1.28.6 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect - github.com/aws/smithy-go v1.27.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -33,7 +40,6 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -75,7 +81,6 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.35.0 // indirect k8s.io/apiextensions-apiserver v0.35.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect diff --git a/go.sum b/go.sum index 5748bb8..01d94b8 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,45 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/aws-controllers-k8s/ec2-controller v1.18.2 h1:vS3Q0LWth+zPzgQ5SJr2IV+c2KJksA8042dDM77D7TY= +github.com/aws-controllers-k8s/ec2-controller v1.18.2/go.mod h1:UnfjqZbu6QT2KGwTDpXwwumvrZHNU4EWa8Xe3yO5k5Q= +github.com/aws-controllers-k8s/iam-controller v1.7.3 h1:tntqjiNnod5R8EtlKXmbEX3yIYbL/FNpJv9znWqEw0c= +github.com/aws-controllers-k8s/iam-controller v1.7.3/go.mod h1:7cuaER+dA91Vdd3qRHO3W94yvRepEMhsS3Mj9GS+K2A= +github.com/aws-controllers-k8s/kms-controller v1.3.3 h1:caavxkk9t/ZjZ80cKNyFurPaqzpnTZO5luEHMQ9QH6E= +github.com/aws-controllers-k8s/kms-controller v1.3.3/go.mod h1:tTsqf4old1Zgg7geHJ1yOtTsu0eWI++pzKLx8ok/VBw= github.com/aws-controllers-k8s/runtime v0.61.0 h1:46ksj9nvW5gx86aB44/zfrZOvJj4G7WWnWf7hj2CcCw= github.com/aws-controllers-k8s/runtime v0.61.0/go.mod h1:Ovpk9GCCc/3hekVN6CL0cZhsG41JqSvIb8DXwaEwL34= +github.com/aws-controllers-k8s/s3-controller v1.8.1 h1:ArnNE4jJieAHVfMlbYuFXEysHC8lj3yRoasjLsA58z0= +github.com/aws-controllers-k8s/s3-controller v1.8.1/go.mod h1:yx66WSLFD6b8eaUFuikb7+dovj6dx8DmitAOJqurXTM= github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= -github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA= -github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg= +github.com/aws/aws-sdk-go-v2 v1.42.1 h1:9eOTgu1z/dVtYpNZ3/8/XbbaX0x/BqE3HUzAzs6K0ek= +github.com/aws/aws-sdk-go-v2 v1.42.1/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM= github.com/aws/aws-sdk-go-v2/config v1.28.6 h1:D89IKtGrs/I3QXOLNTH93NJYtDhm8SYa9Q5CsPShmyo= github.com/aws/aws-sdk-go-v2/config v1.28.6/go.mod h1:GDzxJ5wyyFSCoLkS+UhGB0dArhb9mI+Co4dHtoTxbko= github.com/aws/aws-sdk-go-v2/credentials v1.17.47 h1:48bA+3/fCdi2yAwVt+3COvmatZ6jUDNkDTIsqDiMUdw= github.com/aws/aws-sdk-go-v2/credentials v1.17.47/go.mod h1:+KdckOejLW3Ks3b0E3b5rHsr2f9yuORBum0WPnE5o5w= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 h1:AmoU1pziydclFT/xRV+xXE/Vb8fttJCLRPv8oAkprc0= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21/go.mod h1:AjUdLYe4Tgs6kpH4Bv7uMZo7pottoyHMn4eTcIcneaY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 h1:xM/Is9cKMHa8Jj8zkvWhvrFkZsXJV9E+BB4g0HW0duQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30/go.mod h1:WueJeNDZvK1fMYEWJIkcivBfEzUkTpBhzlrUKKY8EuA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 h1:jn46zC9LdsVR/ZpMIJqMqb8hHv31BlLx3ulVqNspUOk= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30/go.mod h1:1hTMsAgbdS/AtUi4bw8+gUuh1pceo+eXRLfpSuSQj3M= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= +github.com/aws/aws-sdk-go-v2/service/mwaaserverless v1.2.1 h1:x9c6wkbINrJd886BfK+wzs0hg4OYRdQufT+YooBggJE= +github.com/aws/aws-sdk-go-v2/service/mwaaserverless v1.2.1/go.mod h1:V77EedW7bvl09ST+wS99BA7GAseiVeoSWE6vjtPyirM= github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw= github.com/aws/aws-sdk-go-v2/service/sso v1.24.7/go.mod h1:ZHtuQJ6t9A/+YDuxOLnbryAmITtr8UysSny3qcyvJTc= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 h1:JnhTZR3PiYDNKlXy50/pNeix9aGMo6lLpXwJ1mw8MD4= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6/go.mod h1:URronUEGfXZN1VpdktPSD1EkAL9mfrV+2F4sjH38qOY= github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 h1:s4074ZO1Hk8qv65GqNXqDjmkf4HSQqJukaLuuW0TpDA= github.com/aws/aws-sdk-go-v2/service/sts v1.33.2/go.mod h1:mVggCnIWoM09jP71Wh+ea7+5gAp53q+49wDFs1SW5z8= -github.com/aws/smithy-go v1.27.1 h1:4T340VFndXtADGF52gYa1POyL7s9E4Z1OeZ1hCscIw8= -github.com/aws/smithy-go v1.27.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/smithy-go v1.27.3 h1:F3Zb497UhhskkfpJmfkXswyo+t0sh9OTBnIHjogWbVY= +github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= diff --git a/helm/crds/mwaaserverless.services.k8s.aws_workflows.yaml b/helm/crds/mwaaserverless.services.k8s.aws_workflows.yaml new file mode 100644 index 0000000..8a7aa08 --- /dev/null +++ b/helm/crds/mwaaserverless.services.k8s.aws_workflows.yaml @@ -0,0 +1,377 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: workflows.mwaaserverless.services.k8s.aws +spec: + group: mwaaserverless.services.k8s.aws + names: + kind: Workflow + listKind: WorkflowList + plural: workflows + singular: workflow + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.status + name: STATUS + type: string + - jsonPath: .status.version + name: VERSION + type: string + - jsonPath: .status.conditions[?(@.type=="ACK.ResourceSynced")].status + name: Synced + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Workflow is the Schema for the Workflows API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: WorkflowSpec defines the desired state of Workflow. + properties: + definitionS3Location: + description: |- + The Amazon S3 location where the workflow definition file is stored. This + must point to a valid YAML file that defines the workflow structure using + supported Amazon Web Services operators and tasks. Amazon Managed Workflows + for Apache Airflow Serverless takes a snapshot of the definition at creation + time, so subsequent changes to the Amazon S3 object will not affect the workflow + unless you create a new version. In your YAML definition, include task dependencies, + scheduling information, and operator configurations that are compatible with + the Amazon Managed Workflows for Apache Airflow Serverless execution environment. + properties: + bucket: + type: string + bucketRef: + description: Reference field for Bucket + properties: + from: + description: |- + AWSResourceReference provides all the values necessary to reference another + k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + namespace: + type: string + type: object + type: object + objectKey: + type: string + versionID: + type: string + type: object + description: + description: |- + An optional description of the workflow that you can use to provide additional + context about the workflow's purpose and functionality. + + Regex Pattern: `^.+$` + type: string + encryptionConfiguration: + description: |- + The configuration for encrypting workflow data at rest and in transit. Specifies + the encryption type and optional KMS key for customer-managed encryption. + properties: + kmsKeyID: + type: string + kmsKeyRef: + description: Reference field for KMSKeyID + properties: + from: + description: |- + AWSResourceReference provides all the values necessary to reference another + k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + namespace: + type: string + type: object + type: object + type: + type: string + type: object + x-kubernetes-validations: + - message: Value is immutable once set + rule: self == oldSelf + engineVersion: + description: |- + The version of the Amazon Managed Workflows for Apache Airflow Serverless + engine that you want to use for this workflow. This determines the feature + set, supported operators, and execution environment capabilities available + to your workflow. Amazon Managed Workflows for Apache Airflow Serverless + maintains backward compatibility across versions while introducing new features + and improvements. Currently supports version 1 with plans for additional + versions as the service evolves. + format: int64 + type: integer + loggingConfiguration: + description: |- + The configuration for workflow logging. Specifies the CloudWatch log group + where workflow execution logs are stored. Amazon Managed Workflows for Apache + Airflow Serverless automatically exports worker logs and task-level information + to the specified log group in your account using remote logging functionality. + This provides comprehensive observability for debugging and monitoring workflow + execution across the distributed, serverless environment. + properties: + logGroupName: + type: string + type: object + name: + description: |- + The name of the workflow. You must use unique workflow names within your + Amazon Web Services account. The service generates a unique identifier that + is appended to ensure temporal uniqueness across the account lifecycle. + + Regex Pattern: `^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*$` + type: string + x-kubernetes-validations: + - message: Value is immutable once set + rule: self == oldSelf + networkConfiguration: + description: |- + Network configuration for the workflow execution environment, including VPC + security groups and subnets for secure network access. When specified, Amazon + Managed Workflows for Apache Airflow Serverless deploys ECS worker tasks + in your customer VPC to provide secure connectivity to your resources. If + not specified, tasks run in the service's default worker VPC with network + isolation from other customers. This configuration enables secure access + to VPC-only resources like RDS databases or private endpoints. + properties: + securityGroupIDs: + items: + type: string + type: array + securityGroupRefs: + description: Reference field for SecurityGroupIDs + items: + description: "AWSResourceReferenceWrapper provides a wrapper + around *AWSResourceReference\ntype to provide more user friendly + syntax for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ name: my-api" + properties: + from: + description: |- + AWSResourceReference provides all the values necessary to reference another + k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + namespace: + type: string + type: object + type: object + type: array + subnetIDs: + items: + type: string + type: array + subnetRefs: + description: Reference field for SubnetIDs + items: + description: "AWSResourceReferenceWrapper provides a wrapper + around *AWSResourceReference\ntype to provide more user friendly + syntax for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ name: my-api" + properties: + from: + description: |- + AWSResourceReference provides all the values necessary to reference another + k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + namespace: + type: string + type: object + type: object + type: array + type: object + roleARN: + description: |- + The Amazon Resource Name (ARN) of the IAM role that Amazon Managed Workflows + for Apache Airflow Serverless assumes when executing the workflow. This role + must have the necessary permissions to access the required Amazon Web Services + services and resources that your workflow tasks will interact with. The role + is used for task execution in the isolated, multi-tenant environment and + should follow the principle of least privilege. Amazon Managed Workflows + for Apache Airflow Serverless validates role access during workflow creation + but runtime permission checks are performed by the target services. + + Regex Pattern: `^arn:aws(?:-(?:cn|us-gov|iso|iso-b|iso-e|iso-f))?:iam::[0-9]{12}:role(/[a-zA-Z0-9+=,.@_\-]{1,512})*?/[a-zA-Z0-9+=,.@_\-]{1,64}$` + type: string + roleRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference\ntype to provide more user friendly syntax + for references using 'from' field\nEx:\nAPIIDRef:\n\n\tfrom:\n\t + \ name: my-api" + properties: + from: + description: |- + AWSResourceReference provides all the values necessary to reference another + k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + namespace: + type: string + type: object + type: object + tags: + additionalProperties: + type: string + description: |- + A map of tags to assign to the workflow resource. Tags are key-value pairs + that are used for resource organization and cost allocation. + type: object + triggerMode: + description: |- + The trigger mode for the workflow execution. + + Regex Pattern: `^.*$` + type: string + required: + - definitionS3Location + - name + type: object + status: + description: WorkflowStatus defines the observed state of Workflow + properties: + ackResourceMetadata: + description: |- + All CRs managed by ACK have a common `Status.ACKResourceMetadata` member + that is used to contain resource sync state, account ownership, + constructed ARN for the resource + properties: + arn: + description: |- + ARN is the Amazon Resource Name for the resource. This is a + globally-unique identifier and is set only by the ACK service controller + once the controller has orchestrated the creation of the resource OR + when it has verified that an "adopted" resource (a resource where the + ARN annotation was set by the Kubernetes user on the CR) exists and + matches the supplied CR's Spec field values. + https://github.com/aws/aws-controllers-k8s/issues/270 + type: string + ownerAccountID: + description: |- + OwnerAccountID is the AWS Account ID of the account that owns the + backend AWS service API resource. + type: string + partition: + description: Partition is the AWS partition in which the resource + exists or will exist + type: string + region: + description: Region is the AWS region in which the resource exists + or will exist. + type: string + required: + - ownerAccountID + - region + type: object + conditions: + description: |- + All CRs managed by ACK have a common `Status.Conditions` member that + contains a collection of `ackv1alpha1.Condition` objects that describe + the various terminal states of the CR and its backend AWS service API + resource + items: + description: |- + Condition is the common struct used by all CRDs managed by ACK service + controllers to indicate terminal states of the CR and its backend AWS + service API resource + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of the Condition + type: string + required: + - status + - type + type: object + type: array + createdAt: + description: The timestamp when the workflow was created, in ISO 8601 + date-time format. + format: date-time + type: string + modifiedAt: + description: |- + The timestamp when the workflow was last modified, in ISO 8601 date-time + format. + format: date-time + type: string + scheduleConfiguration: + description: |- + The schedule configuration for the workflow, including cron expressions for + automated execution. Amazon Managed Workflows for Apache Airflow Serverless + uses EventBridge Scheduler for cost-effective, timezone-aware scheduling. + When a workflow includes schedule information in its YAML definition, the + service automatically configures the appropriate triggers for automated execution. + Only one version of a workflow can have an active schedule at any given time. + properties: + cronExpression: + type: string + type: object + status: + description: |- + The current status of the workflow. Possible values are READY (workflow is + ready to run) and DELETING (workflow is being deleted). + type: string + version: + description: |- + The version identifier of the workflow. This is a service-generated alphanumeric + string that uniquely identifies this version of the workflow. Amazon Managed + Workflows for Apache Airflow Serverless uses a version-first approach where + each workflow can have multiple immutable versions, which allows you to maintain + different configurations and roll back to previous versions as needed. The + version identifier is used in ARNs and API operations to reference specific + workflow versions. + + Regex Pattern: `^[a-z0-9]{32}$` + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index 73f1de9..861a63c 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -69,6 +69,60 @@ rules: - get - list - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - securitygroups + - securitygroups/status + - subnets + - subnets/status + verbs: + - get + - list +- apiGroups: + - iam.services.k8s.aws + resources: + - roles + - roles/status + verbs: + - get + - list +- apiGroups: + - kms.services.k8s.aws + resources: + - keys + - keys/status + verbs: + - get + - list +- apiGroups: + - mwaaserverless.services.k8s.aws + resources: + - workflows + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mwaaserverless.services.k8s.aws + resources: + - workflows/status + verbs: + - get + - patch + - update +- apiGroups: + - s3.services.k8s.aws + resources: + - buckets + - buckets/status + verbs: + - get + - list - apiGroups: - services.k8s.aws resources: diff --git a/helm/templates/role-reader.yaml b/helm/templates/role-reader.yaml index 6ecfa17..8188668 100644 --- a/helm/templates/role-reader.yaml +++ b/helm/templates/role-reader.yaml @@ -16,6 +16,7 @@ rules: - apiGroups: - mwaaserverless.services.k8s.aws resources: + - workflows verbs: - get - list diff --git a/helm/templates/role-writer.yaml b/helm/templates/role-writer.yaml index 8ac09d2..785da08 100644 --- a/helm/templates/role-writer.yaml +++ b/helm/templates/role-writer.yaml @@ -16,6 +16,7 @@ rules: - apiGroups: - mwaaserverless.services.k8s.aws resources: + - workflows verbs: - create - delete @@ -27,6 +28,7 @@ rules: - apiGroups: - mwaaserverless.services.k8s.aws resources: + - workflows verbs: - get - patch diff --git a/helm/values.yaml b/helm/values.yaml index ec34431..6f05af5 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -150,6 +150,7 @@ reconcile: # If empty, all resources will be reconciled. # If specified, only the listed resource kinds will be reconciled. resources: + - Workflow serviceAccount: # Specifies whether a service account should be created diff --git a/pkg/resource/workflow/delta.go b/pkg/resource/workflow/delta.go new file mode 100644 index 0000000..aae354b --- /dev/null +++ b/pkg/resource/workflow/delta.go @@ -0,0 +1,162 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package workflow + +import ( + "bytes" + + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" + "k8s.io/apimachinery/pkg/api/equality" +) + +// Hack to avoid import errors during build... +var ( + _ = &bytes.Buffer{} + _ = &acktags.Tags{} +) + +// newResourceDelta returns a new `ackcompare.Delta` used to compare two +// resources +func newResourceDelta( + a *resource, + b *resource, +) *ackcompare.Delta { + delta := ackcompare.NewDelta() + if (a == nil && b != nil) || + (a != nil && b == nil) { + delta.Add("", a, b) + return delta + } + + if ackcompare.HasNilDifference(a.ko.Spec.DefinitionS3Location, b.ko.Spec.DefinitionS3Location) { + delta.Add("Spec.DefinitionS3Location", a.ko.Spec.DefinitionS3Location, b.ko.Spec.DefinitionS3Location) + } else if a.ko.Spec.DefinitionS3Location != nil && b.ko.Spec.DefinitionS3Location != nil { + if ackcompare.HasNilDifference(a.ko.Spec.DefinitionS3Location.Bucket, b.ko.Spec.DefinitionS3Location.Bucket) { + delta.Add("Spec.DefinitionS3Location.Bucket", a.ko.Spec.DefinitionS3Location.Bucket, b.ko.Spec.DefinitionS3Location.Bucket) + } else if a.ko.Spec.DefinitionS3Location.Bucket != nil && b.ko.Spec.DefinitionS3Location.Bucket != nil { + if *a.ko.Spec.DefinitionS3Location.Bucket != *b.ko.Spec.DefinitionS3Location.Bucket { + delta.Add("Spec.DefinitionS3Location.Bucket", a.ko.Spec.DefinitionS3Location.Bucket, b.ko.Spec.DefinitionS3Location.Bucket) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.DefinitionS3Location.ObjectKey, b.ko.Spec.DefinitionS3Location.ObjectKey) { + delta.Add("Spec.DefinitionS3Location.ObjectKey", a.ko.Spec.DefinitionS3Location.ObjectKey, b.ko.Spec.DefinitionS3Location.ObjectKey) + } else if a.ko.Spec.DefinitionS3Location.ObjectKey != nil && b.ko.Spec.DefinitionS3Location.ObjectKey != nil { + if *a.ko.Spec.DefinitionS3Location.ObjectKey != *b.ko.Spec.DefinitionS3Location.ObjectKey { + delta.Add("Spec.DefinitionS3Location.ObjectKey", a.ko.Spec.DefinitionS3Location.ObjectKey, b.ko.Spec.DefinitionS3Location.ObjectKey) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.DefinitionS3Location.VersionID, b.ko.Spec.DefinitionS3Location.VersionID) { + delta.Add("Spec.DefinitionS3Location.VersionID", a.ko.Spec.DefinitionS3Location.VersionID, b.ko.Spec.DefinitionS3Location.VersionID) + } else if a.ko.Spec.DefinitionS3Location.VersionID != nil && b.ko.Spec.DefinitionS3Location.VersionID != nil { + if *a.ko.Spec.DefinitionS3Location.VersionID != *b.ko.Spec.DefinitionS3Location.VersionID { + delta.Add("Spec.DefinitionS3Location.VersionID", a.ko.Spec.DefinitionS3Location.VersionID, b.ko.Spec.DefinitionS3Location.VersionID) + } + } + } + if ackcompare.HasNilDifference(a.ko.Spec.Description, b.ko.Spec.Description) { + delta.Add("Spec.Description", a.ko.Spec.Description, b.ko.Spec.Description) + } else if a.ko.Spec.Description != nil && b.ko.Spec.Description != nil { + if *a.ko.Spec.Description != *b.ko.Spec.Description { + delta.Add("Spec.Description", a.ko.Spec.Description, b.ko.Spec.Description) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.EncryptionConfiguration, b.ko.Spec.EncryptionConfiguration) { + delta.Add("Spec.EncryptionConfiguration", a.ko.Spec.EncryptionConfiguration, b.ko.Spec.EncryptionConfiguration) + } else if a.ko.Spec.EncryptionConfiguration != nil && b.ko.Spec.EncryptionConfiguration != nil { + if ackcompare.HasNilDifference(a.ko.Spec.EncryptionConfiguration.KMSKeyID, b.ko.Spec.EncryptionConfiguration.KMSKeyID) { + delta.Add("Spec.EncryptionConfiguration.KMSKeyID", a.ko.Spec.EncryptionConfiguration.KMSKeyID, b.ko.Spec.EncryptionConfiguration.KMSKeyID) + } else if a.ko.Spec.EncryptionConfiguration.KMSKeyID != nil && b.ko.Spec.EncryptionConfiguration.KMSKeyID != nil { + if *a.ko.Spec.EncryptionConfiguration.KMSKeyID != *b.ko.Spec.EncryptionConfiguration.KMSKeyID { + delta.Add("Spec.EncryptionConfiguration.KMSKeyID", a.ko.Spec.EncryptionConfiguration.KMSKeyID, b.ko.Spec.EncryptionConfiguration.KMSKeyID) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.EncryptionConfiguration.Type, b.ko.Spec.EncryptionConfiguration.Type) { + delta.Add("Spec.EncryptionConfiguration.Type", a.ko.Spec.EncryptionConfiguration.Type, b.ko.Spec.EncryptionConfiguration.Type) + } else if a.ko.Spec.EncryptionConfiguration.Type != nil && b.ko.Spec.EncryptionConfiguration.Type != nil { + if *a.ko.Spec.EncryptionConfiguration.Type != *b.ko.Spec.EncryptionConfiguration.Type { + delta.Add("Spec.EncryptionConfiguration.Type", a.ko.Spec.EncryptionConfiguration.Type, b.ko.Spec.EncryptionConfiguration.Type) + } + } + } + if ackcompare.HasNilDifference(a.ko.Spec.EngineVersion, b.ko.Spec.EngineVersion) { + delta.Add("Spec.EngineVersion", a.ko.Spec.EngineVersion, b.ko.Spec.EngineVersion) + } else if a.ko.Spec.EngineVersion != nil && b.ko.Spec.EngineVersion != nil { + if *a.ko.Spec.EngineVersion != *b.ko.Spec.EngineVersion { + delta.Add("Spec.EngineVersion", a.ko.Spec.EngineVersion, b.ko.Spec.EngineVersion) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.LoggingConfiguration, b.ko.Spec.LoggingConfiguration) { + delta.Add("Spec.LoggingConfiguration", a.ko.Spec.LoggingConfiguration, b.ko.Spec.LoggingConfiguration) + } else if a.ko.Spec.LoggingConfiguration != nil && b.ko.Spec.LoggingConfiguration != nil { + if ackcompare.HasNilDifference(a.ko.Spec.LoggingConfiguration.LogGroupName, b.ko.Spec.LoggingConfiguration.LogGroupName) { + delta.Add("Spec.LoggingConfiguration.LogGroupName", a.ko.Spec.LoggingConfiguration.LogGroupName, b.ko.Spec.LoggingConfiguration.LogGroupName) + } else if a.ko.Spec.LoggingConfiguration.LogGroupName != nil && b.ko.Spec.LoggingConfiguration.LogGroupName != nil { + if *a.ko.Spec.LoggingConfiguration.LogGroupName != *b.ko.Spec.LoggingConfiguration.LogGroupName { + delta.Add("Spec.LoggingConfiguration.LogGroupName", a.ko.Spec.LoggingConfiguration.LogGroupName, b.ko.Spec.LoggingConfiguration.LogGroupName) + } + } + } + if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name) { + delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name) + } else if a.ko.Spec.Name != nil && b.ko.Spec.Name != nil { + if *a.ko.Spec.Name != *b.ko.Spec.Name { + delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name) + } + } + if ackcompare.HasNilDifference(a.ko.Spec.NetworkConfiguration, b.ko.Spec.NetworkConfiguration) { + delta.Add("Spec.NetworkConfiguration", a.ko.Spec.NetworkConfiguration, b.ko.Spec.NetworkConfiguration) + } else if a.ko.Spec.NetworkConfiguration != nil && b.ko.Spec.NetworkConfiguration != nil { + if len(a.ko.Spec.NetworkConfiguration.SecurityGroupIDs) != len(b.ko.Spec.NetworkConfiguration.SecurityGroupIDs) { + delta.Add("Spec.NetworkConfiguration.SecurityGroupIDs", a.ko.Spec.NetworkConfiguration.SecurityGroupIDs, b.ko.Spec.NetworkConfiguration.SecurityGroupIDs) + } else if len(a.ko.Spec.NetworkConfiguration.SecurityGroupIDs) > 0 { + if !ackcompare.SliceStringPEqual(a.ko.Spec.NetworkConfiguration.SecurityGroupIDs, b.ko.Spec.NetworkConfiguration.SecurityGroupIDs) { + delta.Add("Spec.NetworkConfiguration.SecurityGroupIDs", a.ko.Spec.NetworkConfiguration.SecurityGroupIDs, b.ko.Spec.NetworkConfiguration.SecurityGroupIDs) + } + } + if len(a.ko.Spec.NetworkConfiguration.SubnetIDs) != len(b.ko.Spec.NetworkConfiguration.SubnetIDs) { + delta.Add("Spec.NetworkConfiguration.SubnetIDs", a.ko.Spec.NetworkConfiguration.SubnetIDs, b.ko.Spec.NetworkConfiguration.SubnetIDs) + } else if len(a.ko.Spec.NetworkConfiguration.SubnetIDs) > 0 { + if !ackcompare.SliceStringPEqual(a.ko.Spec.NetworkConfiguration.SubnetIDs, b.ko.Spec.NetworkConfiguration.SubnetIDs) { + delta.Add("Spec.NetworkConfiguration.SubnetIDs", a.ko.Spec.NetworkConfiguration.SubnetIDs, b.ko.Spec.NetworkConfiguration.SubnetIDs) + } + } + } + if ackcompare.HasNilDifference(a.ko.Spec.RoleARN, b.ko.Spec.RoleARN) { + delta.Add("Spec.RoleARN", a.ko.Spec.RoleARN, b.ko.Spec.RoleARN) + } else if a.ko.Spec.RoleARN != nil && b.ko.Spec.RoleARN != nil { + if *a.ko.Spec.RoleARN != *b.ko.Spec.RoleARN { + delta.Add("Spec.RoleARN", a.ko.Spec.RoleARN, b.ko.Spec.RoleARN) + } + } + if !equality.Semantic.Equalities.DeepEqual(a.ko.Spec.RoleRef, b.ko.Spec.RoleRef) { + delta.Add("Spec.RoleRef", a.ko.Spec.RoleRef, b.ko.Spec.RoleRef) + } + desiredACKTags, _ := convertToOrderedACKTags(a.ko.Spec.Tags) + latestACKTags, _ := convertToOrderedACKTags(b.ko.Spec.Tags) + if !ackcompare.MapStringStringEqual(desiredACKTags, latestACKTags) { + delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags) + } + if ackcompare.HasNilDifference(a.ko.Spec.TriggerMode, b.ko.Spec.TriggerMode) { + delta.Add("Spec.TriggerMode", a.ko.Spec.TriggerMode, b.ko.Spec.TriggerMode) + } else if a.ko.Spec.TriggerMode != nil && b.ko.Spec.TriggerMode != nil { + if *a.ko.Spec.TriggerMode != *b.ko.Spec.TriggerMode { + delta.Add("Spec.TriggerMode", a.ko.Spec.TriggerMode, b.ko.Spec.TriggerMode) + } + } + + return delta +} diff --git a/pkg/resource/workflow/descriptor.go b/pkg/resource/workflow/descriptor.go new file mode 100644 index 0000000..64b640c --- /dev/null +++ b/pkg/resource/workflow/descriptor.go @@ -0,0 +1,155 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package workflow + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + svcapitypes "github.com/aws-controllers-k8s/mwaaserverless-controller/apis/v1alpha1" +) + +const ( + FinalizerString = "finalizers.mwaaserverless.services.k8s.aws/Workflow" +) + +var ( + GroupVersionResource = svcapitypes.GroupVersion.WithResource("workflows") + GroupKind = metav1.GroupKind{ + Group: "mwaaserverless.services.k8s.aws", + Kind: "Workflow", + } +) + +// resourceDescriptor implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceDescriptor` interface +type resourceDescriptor struct { +} + +// GroupVersionKind returns a Kubernetes schema.GroupVersionKind struct that +// describes the API Group, Version and Kind of CRs described by the descriptor +func (d *resourceDescriptor) GroupVersionKind() schema.GroupVersionKind { + return svcapitypes.GroupVersion.WithKind(GroupKind.Kind) +} + +// EmptyRuntimeObject returns an empty object prototype that may be used in +// apimachinery and k8s client operations +func (d *resourceDescriptor) EmptyRuntimeObject() rtclient.Object { + return &svcapitypes.Workflow{} +} + +// ResourceFromRuntimeObject returns an AWSResource that has been initialized +// with the supplied runtime.Object +func (d *resourceDescriptor) ResourceFromRuntimeObject( + obj rtclient.Object, +) acktypes.AWSResource { + return &resource{ + ko: obj.(*svcapitypes.Workflow), + } +} + +// Delta returns an `ackcompare.Delta` object containing the difference between +// one `AWSResource` and another. +func (d *resourceDescriptor) Delta(a, b acktypes.AWSResource) *ackcompare.Delta { + return newResourceDelta(a.(*resource), b.(*resource)) +} + +// IsManaged returns true if the supplied AWSResource is under the management +// of an ACK service controller. What this means in practice is that the +// underlying custom resource (CR) in the AWSResource has had a +// resource-specific finalizer associated with it. +func (d *resourceDescriptor) IsManaged( + res acktypes.AWSResource, +) bool { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeMetaObject in AWSResource") + } + // Remove use of custom code once + // https://github.com/kubernetes-sigs/controller-runtime/issues/994 is + // fixed. This should be able to be: + // + // return k8sctrlutil.ContainsFinalizer(obj, FinalizerString) + return containsFinalizer(obj, FinalizerString) +} + +// Remove once https://github.com/kubernetes-sigs/controller-runtime/issues/994 +// is fixed. +func containsFinalizer(obj rtclient.Object, finalizer string) bool { + f := obj.GetFinalizers() + for _, e := range f { + if e == finalizer { + return true + } + } + return false +} + +// MarkManaged places the supplied resource under the management of ACK. What +// this typically means is that the resource manager will decorate the +// underlying custom resource (CR) with a finalizer that indicates ACK is +// managing the resource and the underlying CR may not be deleted until ACK is +// finished cleaning up any backend AWS service resources associated with the +// CR. +func (d *resourceDescriptor) MarkManaged( + res acktypes.AWSResource, +) { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeMetaObject in AWSResource") + } + k8sctrlutil.AddFinalizer(obj, FinalizerString) +} + +// MarkUnmanaged removes the supplied resource from management by ACK. What +// this typically means is that the resource manager will remove a finalizer +// underlying custom resource (CR) that indicates ACK is managing the resource. +// This will allow the Kubernetes API server to delete the underlying CR. +func (d *resourceDescriptor) MarkUnmanaged( + res acktypes.AWSResource, +) { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeMetaObject in AWSResource") + } + k8sctrlutil.RemoveFinalizer(obj, FinalizerString) +} + +// MarkAdopted places descriptors on the custom resource that indicate the +// resource was not created from within ACK. +func (d *resourceDescriptor) MarkAdopted( + res acktypes.AWSResource, +) { + obj := res.RuntimeObject() + if obj == nil { + // Should not happen. If it does, there is a bug in the code + panic("nil RuntimeObject in AWSResource") + } + curr := obj.GetAnnotations() + if curr == nil { + curr = make(map[string]string) + } + curr[ackv1alpha1.AnnotationAdopted] = "true" + obj.SetAnnotations(curr) +} diff --git a/pkg/resource/workflow/hooks.go b/pkg/resource/workflow/hooks.go new file mode 100644 index 0000000..622a4cb --- /dev/null +++ b/pkg/resource/workflow/hooks.go @@ -0,0 +1,94 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package workflow + +import ( + "context" + + svcsdktags "github.com/aws-controllers-k8s/mwaaserverless-controller/pkg/tags" +) + +// getTags retrieves the tags for the Workflow identified by the supplied ARN. +// +// GetWorkflow does not return tags inline, and ACK does not automatically call +// ListTagsForResource, so the sdk_read_one_post_set_output hook invokes this to +// populate Spec.Tags. The MWAA Serverless API models tags as a flat +// map[string]string; the generated Spec.Tags field is map[string]*string, so we +// convert here. The ListTagsForResource call itself lives in pkg/tags so it can +// be reused if additional resources are added. +func (rm *resourceManager) getTags( + ctx context.Context, + resourceARN string, +) (map[string]*string, error) { + tags, err := svcsdktags.GetTags(ctx, rm.sdkapi, rm.metrics, resourceARN) + if err != nil { + return nil, err + } + if len(tags) == 0 { + return nil, nil + } + out := make(map[string]*string, len(tags)) + for k, v := range tags { + v := v + out[k] = &v + } + return out, nil +} + +// syncTags keeps the AWS resource's tags in sync with Spec.Tags. +// +// Tags on a Workflow are managed exclusively via TagResource/UntagResource; +// UpdateWorkflow has no Tags member. The sdk_update_pre_build_request hook +// invokes this when delta.DifferentAt("Spec.Tags") so that tag drift is +// reconciled rather than looping forever. Spec.Tags is map[string]*string on +// the CRD while the SDK uses map[string]string, so we convert before delegating +// the Tag/Untag reconciliation to pkg/tags. +func (rm *resourceManager) syncTags( + ctx context.Context, + desired *resource, + latest *resource, +) error { + // The ARN is required for both Tag/Untag calls and is only available once + // the resource exists. + if latest.ko.Status.ACKResourceMetadata == nil || + latest.ko.Status.ACKResourceMetadata.ARN == nil { + return nil + } + resourceARN := string(*latest.ko.Status.ACKResourceMetadata.ARN) + + return svcsdktags.SyncTags( + ctx, + rm.sdkapi, + rm.metrics, + resourceARN, + flattenTags(desired.ko.Spec.Tags), + flattenTags(latest.ko.Spec.Tags), + ) +} + +// flattenTags converts a CRD-style map[string]*string tag map into the flat +// map[string]string the MWAA Serverless SDK expects, dropping nil values. +func flattenTags(tags map[string]*string) map[string]string { + if len(tags) == 0 { + return nil + } + out := make(map[string]string, len(tags)) + for k, v := range tags { + if v == nil { + continue + } + out[k] = *v + } + return out +} diff --git a/pkg/resource/workflow/identifiers.go b/pkg/resource/workflow/identifiers.go new file mode 100644 index 0000000..b94d0d9 --- /dev/null +++ b/pkg/resource/workflow/identifiers.go @@ -0,0 +1,64 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package workflow + +import ( + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" +) + +// resourceIdentifiers implements the +// `aws-service-operator-k8s/pkg/types.AWSResourceIdentifiers` interface +type resourceIdentifiers struct { + meta *ackv1alpha1.ResourceMetadata +} + +// ARN returns the AWS Resource Name for the backend AWS resource. If nil, +// this means the resource has not yet been created in the backend AWS +// service. +func (ri *resourceIdentifiers) ARN() *ackv1alpha1.AWSResourceName { + if ri.meta != nil { + return ri.meta.ARN + } + return nil +} + +// OwnerAccountID returns the AWS account identifier in which the +// backend AWS resource resides, or nil if this information is not known +// for the resource +func (ri *resourceIdentifiers) OwnerAccountID() *ackv1alpha1.AWSAccountID { + if ri.meta != nil { + return ri.meta.OwnerAccountID + } + return nil +} + +// Region returns the AWS region in which the resource exists, or +// nil if this information is not known. +func (ri *resourceIdentifiers) Region() *ackv1alpha1.AWSRegion { + if ri.meta != nil { + return ri.meta.Region + } + return nil +} + +// Partition returns the AWS partition in which the reosurce exists, or +// nil if this information is not known. +func (ri *resourceIdentifiers) Partition() *ackv1alpha1.AWSPartition { + if ri.meta != nil { + return ri.meta.Partition + } + return nil +} diff --git a/pkg/resource/workflow/manager.go b/pkg/resource/workflow/manager.go new file mode 100644 index 0000000..6b7d03e --- /dev/null +++ b/pkg/resource/workflow/manager.go @@ -0,0 +1,437 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package workflow + +import ( + "context" + "fmt" + "time" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition" + ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" + ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue" + ackrt "github.com/aws-controllers-k8s/runtime/pkg/runtime" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + ackutil "github.com/aws-controllers-k8s/runtime/pkg/util" + "github.com/aws/aws-sdk-go-v2/aws" + svcsdk "github.com/aws/aws-sdk-go-v2/service/mwaaserverless" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + + svcapitypes "github.com/aws-controllers-k8s/mwaaserverless-controller/apis/v1alpha1" +) + +var ( + _ = ackutil.InStrings + _ = acktags.NewTags() + _ = ackrt.MissingImageTagValue + _ = svcapitypes.Workflow{} +) + +// +kubebuilder:rbac:groups=mwaaserverless.services.k8s.aws,resources=workflows,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mwaaserverless.services.k8s.aws,resources=workflows/status,verbs=get;update;patch + +var lateInitializeFieldNames = []string{"LoggingConfiguration", "TriggerMode"} + +// resourceManager is responsible for providing a consistent way to perform +// CRUD operations in a backend AWS service API for Book custom resources. +type resourceManager struct { + // cfg is a copy of the ackcfg.Config object passed on start of the service + // controller + cfg ackcfg.Config + // clientcfg is a copy of the client configuration passed on start of the + // service controller + clientcfg aws.Config + // log refers to the logr.Logger object handling logging for the service + // controller + log logr.Logger + // metrics contains a collection of Prometheus metric objects that the + // service controller and its reconcilers track + metrics *ackmetrics.Metrics + // rr is the Reconciler which can be used for various utility + // functions such as querying for Secret values given a SecretReference + rr acktypes.Reconciler + // awsAccountID is the AWS account identifier that contains the resources + // managed by this resource manager + awsAccountID ackv1alpha1.AWSAccountID + // The AWS Region that this resource manager targets + awsRegion ackv1alpha1.AWSRegion + // The AWS Partition that this resource manager targets + awsPartition ackv1alpha1.AWSPartition + // sdk is a pointer to the AWS service API client exposed by the + // aws-sdk-go-v2/services/{alias} package. + sdkapi *svcsdk.Client +} + +// concreteResource returns a pointer to a resource from the supplied +// generic AWSResource interface +func (rm *resourceManager) concreteResource( + res acktypes.AWSResource, +) *resource { + // cast the generic interface into a pointer type specific to the concrete + // implementing resource type managed by this resource manager + return res.(*resource) +} + +// ReadOne returns the currently-observed state of the supplied AWSResource in +// the backend AWS service API. +func (rm *resourceManager) ReadOne( + ctx context.Context, + res acktypes.AWSResource, +) (acktypes.AWSResource, error) { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's ReadOne() method received resource with nil CR object") + } + observed, err := rm.sdkFind(ctx, r) + mirrorAWSTags(r, observed) + if err != nil { + if observed != nil { + return rm.onError(observed, err) + } + return rm.onError(r, err) + } + return rm.onSuccess(observed) +} + +// Create attempts to create the supplied AWSResource in the backend AWS +// service API, returning an AWSResource representing the newly-created +// resource +func (rm *resourceManager) Create( + ctx context.Context, + res acktypes.AWSResource, +) (acktypes.AWSResource, error) { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's Create() method received resource with nil CR object") + } + created, err := rm.sdkCreate(ctx, r) + if err != nil { + if created != nil { + return rm.onError(created, err) + } + return rm.onError(r, err) + } + return rm.onSuccess(created) +} + +// Update attempts to mutate the supplied desired AWSResource in the backend AWS +// service API, returning an AWSResource representing the newly-mutated +// resource. +// Note for specialized logic implementers can check to see how the latest +// observed resource differs from the supplied desired state. The +// higher-level reonciler determines whether or not the desired differs +// from the latest observed and decides whether to call the resource +// manager's Update method +func (rm *resourceManager) Update( + ctx context.Context, + resDesired acktypes.AWSResource, + resLatest acktypes.AWSResource, + delta *ackcompare.Delta, +) (acktypes.AWSResource, error) { + desired := rm.concreteResource(resDesired) + latest := rm.concreteResource(resLatest) + if desired.ko == nil || latest.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's Update() method received resource with nil CR object") + } + updated, err := rm.sdkUpdate(ctx, desired, latest, delta) + if err != nil { + if updated != nil { + return rm.onError(updated, err) + } + return rm.onError(latest, err) + } + return rm.onSuccess(updated) +} + +// Delete attempts to destroy the supplied AWSResource in the backend AWS +// service API, returning an AWSResource representing the +// resource being deleted (if delete is asynchronous and takes time) +func (rm *resourceManager) Delete( + ctx context.Context, + res acktypes.AWSResource, +) (acktypes.AWSResource, error) { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's Update() method received resource with nil CR object") + } + observed, err := rm.sdkDelete(ctx, r) + if err != nil { + if observed != nil { + return rm.onError(observed, err) + } + return rm.onError(r, err) + } + + return rm.onSuccess(observed) +} + +// ARNFromName returns an AWS Resource Name from a given string name. This +// is useful for constructing ARNs for APIs that require ARNs in their +// GetAttributes operations but all we have (for new CRs at least) is a +// name for the resource +func (rm *resourceManager) ARNFromName(name string) string { + return fmt.Sprintf( + "arn:%s:mwaaserverless:%s:%s:%s", + rm.awsPartition, + rm.awsRegion, + rm.awsAccountID, + name, + ) +} + +// LateInitialize returns an acktypes.AWSResource after setting the late initialized +// fields from the readOne call. This method will initialize the optional fields +// which were not provided by the k8s user but were defaulted by the AWS service. +// If there are no such fields to be initialized, the returned object is similar to +// object passed in the parameter. +func (rm *resourceManager) LateInitialize( + ctx context.Context, + latest acktypes.AWSResource, +) (acktypes.AWSResource, error) { + rlog := ackrtlog.FromContext(ctx) + // If there are no fields to late initialize, do nothing + if len(lateInitializeFieldNames) == 0 { + rlog.Debug("no late initialization required.") + return latest, nil + } + latestCopy := latest.DeepCopy() + lateInitConditionReason := "" + lateInitConditionMessage := "" + observed, err := rm.ReadOne(ctx, latestCopy) + if err != nil { + lateInitConditionMessage = "Unable to complete Read operation required for late initialization" + lateInitConditionReason = "Late Initialization Failure" + ackcondition.SetLateInitialized(latestCopy, corev1.ConditionFalse, &lateInitConditionMessage, &lateInitConditionReason) + ackcondition.SetSynced(latestCopy, corev1.ConditionFalse, nil, nil) + return latestCopy, err + } + lateInitializedRes := rm.lateInitializeFromReadOneOutput(observed, latestCopy) + incompleteInitialization := rm.incompleteLateInitialization(lateInitializedRes) + if incompleteInitialization { + // Add the condition with LateInitialized=False + lateInitConditionMessage = "Late initialization did not complete, requeuing with delay of 5 seconds" + lateInitConditionReason = "Delayed Late Initialization" + ackcondition.SetLateInitialized(lateInitializedRes, corev1.ConditionFalse, &lateInitConditionMessage, &lateInitConditionReason) + ackcondition.SetSynced(lateInitializedRes, corev1.ConditionFalse, nil, nil) + return lateInitializedRes, ackrequeue.NeededAfter(nil, time.Duration(5)*time.Second) + } + // Set LateInitialized condition to True + lateInitConditionMessage = "Late initialization successful" + lateInitConditionReason = "Late initialization successful" + ackcondition.SetLateInitialized(lateInitializedRes, corev1.ConditionTrue, &lateInitConditionMessage, &lateInitConditionReason) + return lateInitializedRes, nil +} + +// incompleteLateInitialization return true if there are fields which were supposed to be +// late initialized but are not. If all the fields are late initialized, false is returned +func (rm *resourceManager) incompleteLateInitialization( + res acktypes.AWSResource, +) bool { + return false +} + +// lateInitializeFromReadOneOutput late initializes the 'latest' resource from the 'observed' +// resource and returns 'latest' resource +func (rm *resourceManager) lateInitializeFromReadOneOutput( + observed acktypes.AWSResource, + latest acktypes.AWSResource, +) acktypes.AWSResource { + observedKo := rm.concreteResource(observed).ko.DeepCopy() + latestKo := rm.concreteResource(latest).ko.DeepCopy() + if observedKo.Spec.LoggingConfiguration != nil && latestKo.Spec.LoggingConfiguration == nil { + latestKo.Spec.LoggingConfiguration = observedKo.Spec.LoggingConfiguration + } + if observedKo.Spec.TriggerMode != nil && latestKo.Spec.TriggerMode == nil { + latestKo.Spec.TriggerMode = observedKo.Spec.TriggerMode + } + return &resource{latestKo} +} + +// IsSynced returns true if the resource is synced. +func (rm *resourceManager) IsSynced(ctx context.Context, res acktypes.AWSResource) (bool, error) { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's IsSynced() method received resource with nil CR object") + } + + if r.ko.Status.WorkflowStatus == nil { + return false, nil + } + workflowStatusCandidates := []string{"READY"} + if !ackutil.InStrings(*r.ko.Status.WorkflowStatus, workflowStatusCandidates) { + return false, nil + } + + return true, nil +} + +// EnsureTags ensures that tags are present inside the AWSResource. +// If the AWSResource does not have any existing resource tags, the 'tags' +// field is initialized and the controller tags are added. +// If the AWSResource has existing resource tags, then controller tags are +// added to the existing resource tags without overriding them. +// If the AWSResource does not support tags, only then the controller tags +// will not be added to the AWSResource. +func (rm *resourceManager) EnsureTags( + ctx context.Context, + res acktypes.AWSResource, + md acktypes.ServiceControllerMetadata, +) error { + r := rm.concreteResource(res) + if r.ko == nil { + // Should never happen... if it does, it's buggy code. + panic("resource manager's EnsureTags method received resource with nil CR object") + } + defaultTags := ackrt.GetDefaultTags(&rm.cfg, r.ko, md) + var existingTags map[string]*string + existingTags = r.ko.Spec.Tags + resourceTags, keyOrder := convertToOrderedACKTags(existingTags) + tags := acktags.Merge(resourceTags, defaultTags) + r.ko.Spec.Tags = fromACKTags(tags, keyOrder) + return nil +} + +// FilterSystemTags removes system-managed tags from the resource's tag collection +// to prevent the controller from attempting to manage them. This includes: +// - Tags with keys starting with "aws:" (AWS-managed system tags) +// - Tags specified via the --resource-tags startup flag (controller-level tags) +// - Tags injected by AWS services (e.g., CloudFormation, EKS, etc.) +// +// This filtering is essential because: +// 1. AWS services automatically add system tags that cannot be modified by users +// 2. Attempting to remove these tags would result in API errors +// 3. The controller should only manage user-defined tags, not system tags +// +// Must be called after each Read operation to ensure the resource state +// reflects only manageable tags. This prevents unnecessary update attempts +// and maintains consistency between desired and actual resource state. +// +// Example system tags that are filtered: +// - aws:cloudformation:stack-name (CloudFormation) +// - aws:eks:cluster-name (EKS) +// - services.k8s.aws/* (Kubernetes-managed) +func (rm *resourceManager) FilterSystemTags(res acktypes.AWSResource, systemTags []string) { + r := rm.concreteResource(res) + if r == nil || r.ko == nil { + return + } + var existingTags map[string]*string + existingTags = r.ko.Spec.Tags + resourceTags, tagKeyOrder := convertToOrderedACKTags(existingTags) + ignoreSystemTags(resourceTags, systemTags) + r.ko.Spec.Tags = fromACKTags(resourceTags, tagKeyOrder) +} + +// mirrorAWSTags ensures that AWS tags are included in the desired resource +// if they are present in the latest resource. This will ensure that the +// aws tags are not present in a diff. The logic of the controller will +// ensure these tags aren't patched to the resource in the cluster, and +// will only be present to make sure we don't try to remove these tags. +// +// Although there are a lot of similarities between this function and +// EnsureTags, they are very much different. +// While EnsureTags tries to make sure the resource contains the controller +// tags, mirrowAWSTags tries to make sure tags injected by AWS are mirrored +// from the latest resoruce to the desired resource. +func mirrorAWSTags(a *resource, b *resource) { + if a == nil || a.ko == nil || b == nil || b.ko == nil { + return + } + var existingLatestTags map[string]*string + var existingDesiredTags map[string]*string + existingDesiredTags = a.ko.Spec.Tags + existingLatestTags = b.ko.Spec.Tags + desiredTags, desiredTagKeyOrder := convertToOrderedACKTags(existingDesiredTags) + latestTags, _ := convertToOrderedACKTags(existingLatestTags) + syncAWSTags(desiredTags, latestTags) + a.ko.Spec.Tags = fromACKTags(desiredTags, desiredTagKeyOrder) +} + +// newResourceManager returns a new struct implementing +// acktypes.AWSResourceManager +// This is for AWS-SDK-GO-V2 - Created newResourceManager With AWS sdk-Go-ClientV2 +func newResourceManager( + cfg ackcfg.Config, + clientcfg aws.Config, + log logr.Logger, + metrics *ackmetrics.Metrics, + rr acktypes.Reconciler, + id ackv1alpha1.AWSAccountID, + region ackv1alpha1.AWSRegion, +) (*resourceManager, error) { + return &resourceManager{ + cfg: cfg, + clientcfg: clientcfg, + log: log, + metrics: metrics, + rr: rr, + awsAccountID: id, + awsRegion: region, + awsPartition: ackv1alpha1.AWSPartition(cfg.Partition), + sdkapi: svcsdk.NewFromConfig(clientcfg), + }, nil +} + +// onError updates resource conditions and returns updated resource +// it returns nil if no condition is updated. +func (rm *resourceManager) onError( + r *resource, + err error, +) (acktypes.AWSResource, error) { + if r == nil { + return nil, err + } + r1, updated := rm.updateConditions(r, false, err) + if !updated { + return r, err + } + for _, condition := range r1.Conditions() { + if condition.Type == ackv1alpha1.ConditionTypeTerminal && + condition.Status == corev1.ConditionTrue { + // resource is in Terminal condition + // return Terminal error + return r1, ackerr.Terminal + } + } + return r1, err +} + +// onSuccess updates resource conditions and returns updated resource +// it returns the supplied resource if no condition is updated. +func (rm *resourceManager) onSuccess( + r *resource, +) (acktypes.AWSResource, error) { + if r == nil { + return nil, nil + } + r1, updated := rm.updateConditions(r, true, nil) + if !updated { + return r, nil + } + return r1, nil +} diff --git a/pkg/resource/workflow/manager_factory.go b/pkg/resource/workflow/manager_factory.go new file mode 100644 index 0000000..e2ced82 --- /dev/null +++ b/pkg/resource/workflow/manager_factory.go @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package workflow + +import ( + "fmt" + "sync" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" + ackmetrics "github.com/aws-controllers-k8s/runtime/pkg/metrics" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/go-logr/logr" + + svcresource "github.com/aws-controllers-k8s/mwaaserverless-controller/pkg/resource" +) + +// resourceManagerFactory produces resourceManager objects. It implements the +// `types.AWSResourceManagerFactory` interface. +type resourceManagerFactory struct { + sync.RWMutex + // rmCache contains resource managers for a particular AWS account ID + rmCache map[string]*resourceManager +} + +// ResourcePrototype returns an AWSResource that resource managers produced by +// this factory will handle +func (f *resourceManagerFactory) ResourceDescriptor() acktypes.AWSResourceDescriptor { + return &resourceDescriptor{} +} + +// ManagerFor returns a resource manager object that can manage resources for a +// supplied AWS account +func (f *resourceManagerFactory) ManagerFor( + cfg ackcfg.Config, + clientcfg aws.Config, + log logr.Logger, + metrics *ackmetrics.Metrics, + rr acktypes.Reconciler, + id ackv1alpha1.AWSAccountID, + region ackv1alpha1.AWSRegion, + roleARN ackv1alpha1.AWSResourceName, +) (acktypes.AWSResourceManager, error) { + // We use the account ID, region, and role ARN to uniquely identify a + // resource manager. This helps us to avoid creating multiple resource + // managers for the same account/region/roleARN combination. + rmId := fmt.Sprintf("%s/%s/%s", id, region, roleARN) + f.RLock() + rm, found := f.rmCache[rmId] + f.RUnlock() + + if found { + return rm, nil + } + + f.Lock() + defer f.Unlock() + + rm, err := newResourceManager(cfg, clientcfg, log, metrics, rr, id, region) + if err != nil { + return nil, err + } + f.rmCache[rmId] = rm + return rm, nil +} + +// IsAdoptable returns true if the resource is able to be adopted +func (f *resourceManagerFactory) IsAdoptable() bool { + return true +} + +// RequeueOnSuccessSeconds returns true if the resource should be requeued after specified seconds +// Default is false which means resource will not be requeued after success. +func (f *resourceManagerFactory) RequeueOnSuccessSeconds() int { + return 0 +} + +func newResourceManagerFactory() *resourceManagerFactory { + return &resourceManagerFactory{ + rmCache: map[string]*resourceManager{}, + } +} + +func init() { + svcresource.RegisterManagerFactory(newResourceManagerFactory()) +} diff --git a/pkg/resource/workflow/references.go b/pkg/resource/workflow/references.go new file mode 100644 index 0000000..fdc4a53 --- /dev/null +++ b/pkg/resource/workflow/references.go @@ -0,0 +1,648 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package workflow + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + ec2apitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" + iamapitypes "github.com/aws-controllers-k8s/iam-controller/apis/v1alpha1" + kmsapitypes "github.com/aws-controllers-k8s/kms-controller/apis/v1alpha1" + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + ackrt "github.com/aws-controllers-k8s/runtime/pkg/runtime" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + s3apitypes "github.com/aws-controllers-k8s/s3-controller/apis/v1alpha1" + + svcapitypes "github.com/aws-controllers-k8s/mwaaserverless-controller/apis/v1alpha1" +) + +// +kubebuilder:rbac:groups=s3.services.k8s.aws,resources=buckets,verbs=get;list +// +kubebuilder:rbac:groups=s3.services.k8s.aws,resources=buckets/status,verbs=get;list + +// +kubebuilder:rbac:groups=kms.services.k8s.aws,resources=keys,verbs=get;list +// +kubebuilder:rbac:groups=kms.services.k8s.aws,resources=keys/status,verbs=get;list + +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=securitygroups,verbs=get;list +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=securitygroups/status,verbs=get;list + +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=subnets,verbs=get;list +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=subnets/status,verbs=get;list + +// +kubebuilder:rbac:groups=iam.services.k8s.aws,resources=roles,verbs=get;list +// +kubebuilder:rbac:groups=iam.services.k8s.aws,resources=roles/status,verbs=get;list + +// ClearResolvedReferences removes any reference values that were made +// concrete in the spec. It returns a copy of the input AWSResource which +// contains the original *Ref values, but none of their respective concrete +// values. +func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { + ko := rm.concreteResource(res).ko.DeepCopy() + + if ko.Spec.DefinitionS3Location != nil { + if ko.Spec.DefinitionS3Location.BucketRef != nil { + ko.Spec.DefinitionS3Location.Bucket = nil + } + } + + if ko.Spec.EncryptionConfiguration != nil { + if ko.Spec.EncryptionConfiguration.KMSKeyRef != nil { + ko.Spec.EncryptionConfiguration.KMSKeyID = nil + } + } + + if ko.Spec.NetworkConfiguration != nil { + if len(ko.Spec.NetworkConfiguration.SecurityGroupRefs) > 0 { + ko.Spec.NetworkConfiguration.SecurityGroupIDs = nil + } + } + + if ko.Spec.NetworkConfiguration != nil { + if len(ko.Spec.NetworkConfiguration.SubnetRefs) > 0 { + ko.Spec.NetworkConfiguration.SubnetIDs = nil + } + } + + if ko.Spec.RoleRef != nil { + ko.Spec.RoleARN = nil + } + + return &resource{ko} +} + +// ResolveReferences finds if there are any Reference field(s) present +// inside AWSResource passed in the parameter and attempts to resolve those +// reference field(s) into their respective target field(s). It returns a +// copy of the input AWSResource with resolved reference(s), a boolean which +// is set to true if the resource contains any references (regardless of if +// they are resolved successfully) and an error if the passed AWSResource's +// reference field(s) could not be resolved. +func (rm *resourceManager) ResolveReferences( + ctx context.Context, + apiReader client.Reader, + res acktypes.AWSResource, +) (acktypes.AWSResource, bool, error) { + ko := rm.concreteResource(res).ko + + resourceHasReferences := false + err := validateReferenceFields(ko) + if fieldHasReferences, err := rm.resolveReferenceForDefinitionS3Location_Bucket(ctx, apiReader, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + if fieldHasReferences, err := rm.resolveReferenceForEncryptionConfiguration_KMSKeyID(ctx, apiReader, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + if fieldHasReferences, err := rm.resolveReferenceForNetworkConfiguration_SecurityGroupIDs(ctx, apiReader, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + if fieldHasReferences, err := rm.resolveReferenceForNetworkConfiguration_SubnetIDs(ctx, apiReader, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + if fieldHasReferences, err := rm.resolveReferenceForRoleARN(ctx, apiReader, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + return &resource{ko}, resourceHasReferences, err +} + +// validateReferenceFields validates the reference field and corresponding +// identifier field. +func validateReferenceFields(ko *svcapitypes.Workflow) error { + + if ko.Spec.DefinitionS3Location != nil { + if ko.Spec.DefinitionS3Location.BucketRef != nil && ko.Spec.DefinitionS3Location.Bucket != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("DefinitionS3Location.Bucket", "DefinitionS3Location.BucketRef") + } + } + + if ko.Spec.EncryptionConfiguration != nil { + if ko.Spec.EncryptionConfiguration.KMSKeyRef != nil && ko.Spec.EncryptionConfiguration.KMSKeyID != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("EncryptionConfiguration.KMSKeyID", "EncryptionConfiguration.KMSKeyRef") + } + } + + if ko.Spec.NetworkConfiguration != nil { + if len(ko.Spec.NetworkConfiguration.SecurityGroupRefs) > 0 && len(ko.Spec.NetworkConfiguration.SecurityGroupIDs) > 0 { + return ackerr.ResourceReferenceAndIDNotSupportedFor("NetworkConfiguration.SecurityGroupIDs", "NetworkConfiguration.SecurityGroupRefs") + } + } + + if ko.Spec.NetworkConfiguration != nil { + if len(ko.Spec.NetworkConfiguration.SubnetRefs) > 0 && len(ko.Spec.NetworkConfiguration.SubnetIDs) > 0 { + return ackerr.ResourceReferenceAndIDNotSupportedFor("NetworkConfiguration.SubnetIDs", "NetworkConfiguration.SubnetRefs") + } + } + + if ko.Spec.RoleRef != nil && ko.Spec.RoleARN != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("RoleARN", "RoleRef") + } + if ko.Spec.RoleRef == nil && ko.Spec.RoleARN == nil { + return ackerr.ResourceReferenceOrIDRequiredFor("RoleARN", "RoleRef") + } + return nil +} + +// resolveReferenceForDefinitionS3Location_Bucket reads the resource referenced +// from DefinitionS3Location.BucketRef field and sets the DefinitionS3Location.Bucket +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForDefinitionS3Location_Bucket( + ctx context.Context, + apiReader client.Reader, + ko *svcapitypes.Workflow, +) (hasReferences bool, err error) { + if ko.Spec.DefinitionS3Location != nil { + if ko.Spec.DefinitionS3Location.BucketRef != nil && ko.Spec.DefinitionS3Location.BucketRef.From != nil { + hasReferences = true + arr := ko.Spec.DefinitionS3Location.BucketRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: DefinitionS3Location.BucketRef") + } + namespace, err := ackrt.ResolveCrossNamespaceReference( + ctx, + rm.cfg.EnableCrossNamespace, + &ko.Status.Conditions, + ackrt.CrossNamespaceRefKindResource, + ko.ObjectMeta.GetNamespace(), + arr.Namespace, + *arr.Name, + ) + if err != nil { + return hasReferences, err + } + obj := &s3apitypes.Bucket{} + if err := getReferencedResourceState_Bucket(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.DefinitionS3Location.Bucket = (*string)(obj.Spec.Name) + } + } + + return hasReferences, nil +} + +// getReferencedResourceState_Bucket looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_Bucket( + ctx context.Context, + apiReader client.Reader, + obj *s3apitypes.Bucket, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "Bucket", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "Bucket", + namespace, name) + } + var refResourceSynced bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "Bucket", + namespace, name) + } + if obj.Spec.Name == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "Bucket", + namespace, name, + "Spec.Name") + } + return nil +} + +// resolveReferenceForEncryptionConfiguration_KMSKeyID reads the resource referenced +// from EncryptionConfiguration.KMSKeyRef field and sets the EncryptionConfiguration.KMSKeyID +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForEncryptionConfiguration_KMSKeyID( + ctx context.Context, + apiReader client.Reader, + ko *svcapitypes.Workflow, +) (hasReferences bool, err error) { + if ko.Spec.EncryptionConfiguration != nil { + if ko.Spec.EncryptionConfiguration.KMSKeyRef != nil && ko.Spec.EncryptionConfiguration.KMSKeyRef.From != nil { + hasReferences = true + arr := ko.Spec.EncryptionConfiguration.KMSKeyRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: EncryptionConfiguration.KMSKeyRef") + } + namespace, err := ackrt.ResolveCrossNamespaceReference( + ctx, + rm.cfg.EnableCrossNamespace, + &ko.Status.Conditions, + ackrt.CrossNamespaceRefKindResource, + ko.ObjectMeta.GetNamespace(), + arr.Namespace, + *arr.Name, + ) + if err != nil { + return hasReferences, err + } + obj := &kmsapitypes.Key{} + if err := getReferencedResourceState_Key(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.EncryptionConfiguration.KMSKeyID = (*string)(obj.Status.ACKResourceMetadata.ARN) + } + } + + return hasReferences, nil +} + +// getReferencedResourceState_Key looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_Key( + ctx context.Context, + apiReader client.Reader, + obj *kmsapitypes.Key, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "Key", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "Key", + namespace, name) + } + var refResourceSynced bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "Key", + namespace, name) + } + if obj.Status.ACKResourceMetadata == nil || obj.Status.ACKResourceMetadata.ARN == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "Key", + namespace, name, + "Status.ACKResourceMetadata.ARN") + } + return nil +} + +// resolveReferenceForNetworkConfiguration_SecurityGroupIDs reads the resource referenced +// from NetworkConfiguration.SecurityGroupRefs field and sets the NetworkConfiguration.SecurityGroupIDs +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForNetworkConfiguration_SecurityGroupIDs( + ctx context.Context, + apiReader client.Reader, + ko *svcapitypes.Workflow, +) (hasReferences bool, err error) { + if ko.Spec.NetworkConfiguration != nil { + for _, f0iter := range ko.Spec.NetworkConfiguration.SecurityGroupRefs { + if f0iter != nil && f0iter.From != nil { + hasReferences = true + arr := f0iter.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: NetworkConfiguration.SecurityGroupRefs") + } + namespace, err := ackrt.ResolveCrossNamespaceReference( + ctx, + rm.cfg.EnableCrossNamespace, + &ko.Status.Conditions, + ackrt.CrossNamespaceRefKindResource, + ko.ObjectMeta.GetNamespace(), + arr.Namespace, + *arr.Name, + ) + if err != nil { + return hasReferences, err + } + obj := &ec2apitypes.SecurityGroup{} + if err := getReferencedResourceState_SecurityGroup(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + if ko.Spec.NetworkConfiguration.SecurityGroupIDs == nil { + ko.Spec.NetworkConfiguration.SecurityGroupIDs = make([]*string, 0, 1) + } + ko.Spec.NetworkConfiguration.SecurityGroupIDs = append(ko.Spec.NetworkConfiguration.SecurityGroupIDs, (*string)(obj.Status.ID)) + } + } + } + + return hasReferences, nil +} + +// getReferencedResourceState_SecurityGroup looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_SecurityGroup( + ctx context.Context, + apiReader client.Reader, + obj *ec2apitypes.SecurityGroup, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "SecurityGroup", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "SecurityGroup", + namespace, name) + } + var refResourceSynced bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "SecurityGroup", + namespace, name) + } + if obj.Status.ID == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "SecurityGroup", + namespace, name, + "Status.ID") + } + return nil +} + +// resolveReferenceForNetworkConfiguration_SubnetIDs reads the resource referenced +// from NetworkConfiguration.SubnetRefs field and sets the NetworkConfiguration.SubnetIDs +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForNetworkConfiguration_SubnetIDs( + ctx context.Context, + apiReader client.Reader, + ko *svcapitypes.Workflow, +) (hasReferences bool, err error) { + if ko.Spec.NetworkConfiguration != nil { + for _, f0iter := range ko.Spec.NetworkConfiguration.SubnetRefs { + if f0iter != nil && f0iter.From != nil { + hasReferences = true + arr := f0iter.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: NetworkConfiguration.SubnetRefs") + } + namespace, err := ackrt.ResolveCrossNamespaceReference( + ctx, + rm.cfg.EnableCrossNamespace, + &ko.Status.Conditions, + ackrt.CrossNamespaceRefKindResource, + ko.ObjectMeta.GetNamespace(), + arr.Namespace, + *arr.Name, + ) + if err != nil { + return hasReferences, err + } + obj := &ec2apitypes.Subnet{} + if err := getReferencedResourceState_Subnet(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + if ko.Spec.NetworkConfiguration.SubnetIDs == nil { + ko.Spec.NetworkConfiguration.SubnetIDs = make([]*string, 0, 1) + } + ko.Spec.NetworkConfiguration.SubnetIDs = append(ko.Spec.NetworkConfiguration.SubnetIDs, (*string)(obj.Status.SubnetID)) + } + } + } + + return hasReferences, nil +} + +// getReferencedResourceState_Subnet looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_Subnet( + ctx context.Context, + apiReader client.Reader, + obj *ec2apitypes.Subnet, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "Subnet", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "Subnet", + namespace, name) + } + var refResourceSynced bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "Subnet", + namespace, name) + } + if obj.Status.SubnetID == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "Subnet", + namespace, name, + "Status.SubnetID") + } + return nil +} + +// resolveReferenceForRoleARN reads the resource referenced +// from RoleRef field and sets the RoleARN +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForRoleARN( + ctx context.Context, + apiReader client.Reader, + ko *svcapitypes.Workflow, +) (hasReferences bool, err error) { + if ko.Spec.RoleRef != nil && ko.Spec.RoleRef.From != nil { + hasReferences = true + arr := ko.Spec.RoleRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: RoleRef") + } + namespace, err := ackrt.ResolveCrossNamespaceReference( + ctx, + rm.cfg.EnableCrossNamespace, + &ko.Status.Conditions, + ackrt.CrossNamespaceRefKindResource, + ko.ObjectMeta.GetNamespace(), + arr.Namespace, + *arr.Name, + ) + if err != nil { + return hasReferences, err + } + obj := &iamapitypes.Role{} + if err := getReferencedResourceState_Role(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.RoleARN = (*string)(obj.Status.ACKResourceMetadata.ARN) + } + + return hasReferences, nil +} + +// getReferencedResourceState_Role looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_Role( + ctx context.Context, + apiReader client.Reader, + obj *iamapitypes.Role, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "Role", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "Role", + namespace, name) + } + var refResourceSynced bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "Role", + namespace, name) + } + if obj.Status.ACKResourceMetadata == nil || obj.Status.ACKResourceMetadata.ARN == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "Role", + namespace, name, + "Status.ACKResourceMetadata.ARN") + } + return nil +} diff --git a/pkg/resource/workflow/resource.go b/pkg/resource/workflow/resource.go new file mode 100644 index 0000000..d4be439 --- /dev/null +++ b/pkg/resource/workflow/resource.go @@ -0,0 +1,118 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package workflow + +import ( + "fmt" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackerrors "github.com/aws-controllers-k8s/runtime/pkg/errors" + acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rtclient "sigs.k8s.io/controller-runtime/pkg/client" + + svcapitypes "github.com/aws-controllers-k8s/mwaaserverless-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &ackerrors.MissingNameIdentifier +) + +// resource implements the `aws-controller-k8s/runtime/pkg/types.AWSResource` +// interface +type resource struct { + // The Kubernetes-native CR representing the resource + ko *svcapitypes.Workflow +} + +// Identifiers returns an AWSResourceIdentifiers object containing various +// identifying information, including the AWS account ID that owns the +// resource, the resource's AWS Resource Name (ARN) +func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { + return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} +} + +// IsBeingDeleted returns true if the Kubernetes resource has a non-zero +// deletion timestamp +func (r *resource) IsBeingDeleted() bool { + return !r.ko.DeletionTimestamp.IsZero() +} + +// RuntimeObject returns the Kubernetes apimachinery/runtime representation of +// the AWSResource +func (r *resource) RuntimeObject() rtclient.Object { + return r.ko +} + +// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object +// representation of the AWSResource +func (r *resource) MetaObject() metav1.Object { + return r.ko.GetObjectMeta() +} + +// Conditions returns the ACK Conditions collection for the AWSResource +func (r *resource) Conditions() []*ackv1alpha1.Condition { + return r.ko.Status.Conditions +} + +// ReplaceConditions sets the Conditions status field for the resource +func (r *resource) ReplaceConditions(conditions []*ackv1alpha1.Condition) { + r.ko.Status.Conditions = conditions +} + +// SetObjectMeta sets the ObjectMeta field for the resource +func (r *resource) SetObjectMeta(meta metav1.ObjectMeta) { + r.ko.ObjectMeta = meta +} + +// SetStatus will set the Status field for the resource +func (r *resource) SetStatus(desired acktypes.AWSResource) { + r.ko.Status = desired.(*resource).ko.Status +} + +// SetIdentifiers sets the Spec or Status field that is referenced as the unique +// resource identifier +func (r *resource) SetIdentifiers(identifier *ackv1alpha1.AWSIdentifiers) error { + if r.ko.Status.ACKResourceMetadata == nil { + r.ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + r.ko.Status.ACKResourceMetadata.ARN = identifier.ARN + + return nil +} + +// PopulateResourceFromAnnotation populates the fields passed from adoption annotation +func (r *resource) PopulateResourceFromAnnotation(fields map[string]string) error { + resourceARN, ok := fields["arn"] + if !ok { + return ackerrors.NewTerminalError(fmt.Errorf("required field missing: arn")) + } + + if r.ko.Status.ACKResourceMetadata == nil { + r.ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + arn := ackv1alpha1.AWSResourceName(resourceARN) + r.ko.Status.ACKResourceMetadata.ARN = &arn + + return nil +} + +// DeepCopy will return a copy of the resource +func (r *resource) DeepCopy() acktypes.AWSResource { + koCopy := r.ko.DeepCopy() + return &resource{koCopy} +} diff --git a/pkg/resource/workflow/sdk.go b/pkg/resource/workflow/sdk.go new file mode 100644 index 0000000..bda0b91 --- /dev/null +++ b/pkg/resource/workflow/sdk.go @@ -0,0 +1,669 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package workflow + +import ( + "context" + "errors" + "fmt" + "math" + "reflect" + "strings" + + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" + ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + "github.com/aws/aws-sdk-go-v2/aws" + svcsdk "github.com/aws/aws-sdk-go-v2/service/mwaaserverless" + svcsdktypes "github.com/aws/aws-sdk-go-v2/service/mwaaserverless/types" + smithy "github.com/aws/smithy-go" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + svcapitypes "github.com/aws-controllers-k8s/mwaaserverless-controller/apis/v1alpha1" +) + +// Hack to avoid import errors during build... +var ( + _ = &metav1.Time{} + _ = strings.ToLower("") + _ = &svcsdk.Client{} + _ = &svcapitypes.Workflow{} + _ = ackv1alpha1.AWSAccountID("") + _ = &ackerr.NotFound + _ = &ackcondition.NotManagedMessage + _ = &reflect.Value{} + _ = fmt.Sprintf("") + _ = &ackrequeue.NoRequeue{} + _ = &aws.Config{} +) + +// sdkFind returns SDK-specific information about a supplied resource +func (rm *resourceManager) sdkFind( + ctx context.Context, + r *resource, +) (latest *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkFind") + defer func() { + exit(err) + }() + // If any required fields in the input shape are missing, AWS resource is + // not created yet. Return NotFound here to indicate to callers that the + // resource isn't yet created. + if rm.requiredFieldsMissingFromReadOneInput(r) { + return nil, ackerr.NotFound + } + + input, err := rm.newDescribeRequestPayload(r) + if err != nil { + return nil, err + } + + var resp *svcsdk.GetWorkflowOutput + resp, err = rm.sdkapi.GetWorkflow(ctx, input) + rm.metrics.RecordAPICall("READ_ONE", "GetWorkflow", err) + if err != nil { + var awsErr smithy.APIError + if errors.As(err, &awsErr) && awsErr.ErrorCode() == "ResourceNotFoundException" { + return nil, ackerr.NotFound + } + return nil, err + } + + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := r.ko.DeepCopy() + + if resp.CreatedAt != nil { + ko.Status.CreatedAt = &metav1.Time{*resp.CreatedAt} + } else { + ko.Status.CreatedAt = nil + } + if resp.DefinitionS3Location != nil { + f1 := &svcapitypes.DefinitionS3Location{} + if resp.DefinitionS3Location.Bucket != nil { + f1.Bucket = resp.DefinitionS3Location.Bucket + } + if resp.DefinitionS3Location.ObjectKey != nil { + f1.ObjectKey = resp.DefinitionS3Location.ObjectKey + } + if resp.DefinitionS3Location.VersionId != nil { + f1.VersionID = resp.DefinitionS3Location.VersionId + } + ko.Spec.DefinitionS3Location = f1 + } else { + ko.Spec.DefinitionS3Location = nil + } + if resp.Description != nil { + ko.Spec.Description = resp.Description + } else { + ko.Spec.Description = nil + } + if resp.EncryptionConfiguration != nil { + f3 := &svcapitypes.EncryptionConfiguration{} + if resp.EncryptionConfiguration.KmsKeyId != nil { + f3.KMSKeyID = resp.EncryptionConfiguration.KmsKeyId + } + if resp.EncryptionConfiguration.Type != "" { + f3.Type = aws.String(string(resp.EncryptionConfiguration.Type)) + } + ko.Spec.EncryptionConfiguration = f3 + } else { + ko.Spec.EncryptionConfiguration = nil + } + engineVersionCopy := int64(resp.EngineVersion) + ko.Spec.EngineVersion = &engineVersionCopy + if resp.LoggingConfiguration != nil { + f5 := &svcapitypes.LoggingConfiguration{} + if resp.LoggingConfiguration.LogGroupName != nil { + f5.LogGroupName = resp.LoggingConfiguration.LogGroupName + } + ko.Spec.LoggingConfiguration = f5 + } else { + ko.Spec.LoggingConfiguration = nil + } + if resp.ModifiedAt != nil { + ko.Status.ModifiedAt = &metav1.Time{*resp.ModifiedAt} + } else { + ko.Status.ModifiedAt = nil + } + if resp.Name != nil { + ko.Spec.Name = resp.Name + } else { + ko.Spec.Name = nil + } + if resp.NetworkConfiguration != nil { + f8 := &svcapitypes.NetworkConfiguration{} + if resp.NetworkConfiguration.SecurityGroupIds != nil { + f8.SecurityGroupIDs = aws.StringSlice(resp.NetworkConfiguration.SecurityGroupIds) + } + if resp.NetworkConfiguration.SubnetIds != nil { + f8.SubnetIDs = aws.StringSlice(resp.NetworkConfiguration.SubnetIds) + } + ko.Spec.NetworkConfiguration = f8 + } else { + ko.Spec.NetworkConfiguration = nil + } + if resp.RoleArn != nil { + ko.Spec.RoleARN = resp.RoleArn + } else { + ko.Spec.RoleARN = nil + } + if resp.ScheduleConfiguration != nil { + f10 := &svcapitypes.ScheduleConfiguration{} + if resp.ScheduleConfiguration.CronExpression != nil { + f10.CronExpression = resp.ScheduleConfiguration.CronExpression + } + ko.Status.ScheduleConfiguration = f10 + } else { + ko.Status.ScheduleConfiguration = nil + } + if resp.TriggerMode != nil { + ko.Spec.TriggerMode = resp.TriggerMode + } else { + ko.Spec.TriggerMode = nil + } + if ko.Status.ACKResourceMetadata == nil { + ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + if resp.WorkflowArn != nil { + arn := ackv1alpha1.AWSResourceName(*resp.WorkflowArn) + ko.Status.ACKResourceMetadata.ARN = &arn + } + if resp.WorkflowStatus != "" { + ko.Status.WorkflowStatus = aws.String(string(resp.WorkflowStatus)) + } else { + ko.Status.WorkflowStatus = nil + } + if resp.WorkflowVersion != nil { + ko.Status.WorkflowVersion = resp.WorkflowVersion + } else { + ko.Status.WorkflowVersion = nil + } + + rm.setStatusDefaults(ko) + if ko.Status.ACKResourceMetadata != nil && ko.Status.ACKResourceMetadata.ARN != nil { + tags, err := rm.getTags(ctx, string(*ko.Status.ACKResourceMetadata.ARN)) + if err != nil { + return nil, err + } + ko.Spec.Tags = tags + } + + // GetWorkflow never returns EngineVersion, so the generated read fabricates + // EngineVersion=0. Treat that fabricated zero as "not observed" and mirror + // the desired value onto the read: an unset field stays absent (no &0 + // poisoning the CR spec / no UpdateWorkflow sending EngineVersion=0), and a + // set field round-trips cleanly with no phantom delta. If MWAA ever starts + // echoing a real (non-zero) EngineVersion the normalization steps aside and + // the generated comparator detects genuine drift. + if ko.Spec.EngineVersion == nil || *ko.Spec.EngineVersion == 0 { + ko.Spec.EngineVersion = r.ko.Spec.EngineVersion + } + + return &resource{ko}, nil +} + +// requiredFieldsMissingFromReadOneInput returns true if there are any fields +// for the ReadOne Input shape that are required but not present in the +// resource's Spec or Status +func (rm *resourceManager) requiredFieldsMissingFromReadOneInput( + r *resource, +) bool { + return (r.ko.Status.ACKResourceMetadata == nil || r.ko.Status.ACKResourceMetadata.ARN == nil) + +} + +// newDescribeRequestPayload returns SDK-specific struct for the HTTP request +// payload of the Describe API call for the resource +func (rm *resourceManager) newDescribeRequestPayload( + r *resource, +) (*svcsdk.GetWorkflowInput, error) { + res := &svcsdk.GetWorkflowInput{} + + if r.ko.Status.ACKResourceMetadata != nil && r.ko.Status.ACKResourceMetadata.ARN != nil { + res.WorkflowArn = (*string)(r.ko.Status.ACKResourceMetadata.ARN) + } + if r.ko.Status.WorkflowVersion != nil { + res.WorkflowVersion = r.ko.Status.WorkflowVersion + } + + return res, nil +} + +// sdkCreate creates the supplied resource in the backend AWS service API and +// returns a copy of the resource with resource fields (in both Spec and +// Status) filled in with values from the CREATE API operation's Output shape. +func (rm *resourceManager) sdkCreate( + ctx context.Context, + desired *resource, +) (created *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkCreate") + defer func() { + exit(err) + }() + input, err := rm.newCreateRequestPayload(ctx, desired) + if err != nil { + return nil, err + } + + var resp *svcsdk.CreateWorkflowOutput + _ = resp + resp, err = rm.sdkapi.CreateWorkflow(ctx, input) + rm.metrics.RecordAPICall("CREATE", "CreateWorkflow", err) + if err != nil { + return nil, err + } + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := desired.ko.DeepCopy() + + if resp.CreatedAt != nil { + ko.Status.CreatedAt = &metav1.Time{*resp.CreatedAt} + } else { + ko.Status.CreatedAt = nil + } + if ko.Status.ACKResourceMetadata == nil { + ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + if resp.WorkflowArn != nil { + arn := ackv1alpha1.AWSResourceName(*resp.WorkflowArn) + ko.Status.ACKResourceMetadata.ARN = &arn + } + if resp.WorkflowStatus != "" { + ko.Status.WorkflowStatus = aws.String(string(resp.WorkflowStatus)) + } else { + ko.Status.WorkflowStatus = nil + } + if resp.WorkflowVersion != nil { + ko.Status.WorkflowVersion = resp.WorkflowVersion + } else { + ko.Status.WorkflowVersion = nil + } + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newCreateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Create API call for the resource +func (rm *resourceManager) newCreateRequestPayload( + ctx context.Context, + r *resource, +) (*svcsdk.CreateWorkflowInput, error) { + res := &svcsdk.CreateWorkflowInput{} + + if r.ko.Spec.DefinitionS3Location != nil { + f1 := &svcsdktypes.DefinitionS3Location{} + if r.ko.Spec.DefinitionS3Location.Bucket != nil { + f1.Bucket = r.ko.Spec.DefinitionS3Location.Bucket + } + if r.ko.Spec.DefinitionS3Location.ObjectKey != nil { + f1.ObjectKey = r.ko.Spec.DefinitionS3Location.ObjectKey + } + if r.ko.Spec.DefinitionS3Location.VersionID != nil { + f1.VersionId = r.ko.Spec.DefinitionS3Location.VersionID + } + res.DefinitionS3Location = f1 + } + if r.ko.Spec.Description != nil { + res.Description = r.ko.Spec.Description + } + if r.ko.Spec.EncryptionConfiguration != nil { + f3 := &svcsdktypes.EncryptionConfiguration{} + if r.ko.Spec.EncryptionConfiguration.KMSKeyID != nil { + f3.KmsKeyId = r.ko.Spec.EncryptionConfiguration.KMSKeyID + } + if r.ko.Spec.EncryptionConfiguration.Type != nil { + f3.Type = svcsdktypes.EncryptionType(*r.ko.Spec.EncryptionConfiguration.Type) + } + res.EncryptionConfiguration = f3 + } + if r.ko.Spec.EngineVersion != nil { + engineVersionCopy0 := *r.ko.Spec.EngineVersion + if engineVersionCopy0 > math.MaxInt32 || engineVersionCopy0 < math.MinInt32 { + return nil, fmt.Errorf("error: field EngineVersion is of type int32") + } + engineVersionCopy := int32(engineVersionCopy0) + res.EngineVersion = engineVersionCopy + } + if r.ko.Spec.LoggingConfiguration != nil { + f5 := &svcsdktypes.LoggingConfiguration{} + if r.ko.Spec.LoggingConfiguration.LogGroupName != nil { + f5.LogGroupName = r.ko.Spec.LoggingConfiguration.LogGroupName + } + res.LoggingConfiguration = f5 + } + if r.ko.Spec.Name != nil { + res.Name = r.ko.Spec.Name + } + if r.ko.Spec.NetworkConfiguration != nil { + f7 := &svcsdktypes.NetworkConfiguration{} + if r.ko.Spec.NetworkConfiguration.SecurityGroupIDs != nil { + f7.SecurityGroupIds = aws.ToStringSlice(r.ko.Spec.NetworkConfiguration.SecurityGroupIDs) + } + if r.ko.Spec.NetworkConfiguration.SubnetIDs != nil { + f7.SubnetIds = aws.ToStringSlice(r.ko.Spec.NetworkConfiguration.SubnetIDs) + } + res.NetworkConfiguration = f7 + } + if r.ko.Spec.RoleARN != nil { + res.RoleArn = r.ko.Spec.RoleARN + } + if r.ko.Spec.Tags != nil { + res.Tags = aws.ToStringMap(r.ko.Spec.Tags) + } + if r.ko.Spec.TriggerMode != nil { + res.TriggerMode = r.ko.Spec.TriggerMode + } + + return res, nil +} + +// sdkUpdate patches the supplied resource in the backend AWS service API and +// returns a new resource with updated fields. +func (rm *resourceManager) sdkUpdate( + ctx context.Context, + desired *resource, + latest *resource, + delta *ackcompare.Delta, +) (updated *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkUpdate") + defer func() { + exit(err) + }() + // Tags are managed via TagResource/UntagResource, not UpdateWorkflow. Sync + // any tag drift here so the resource doesn't reconcile forever. + if delta.DifferentAt("Spec.Tags") { + if err = rm.syncTags(ctx, desired, latest); err != nil { + return nil, err + } + } + // If ONLY tags changed there is nothing for UpdateWorkflow to do, so + // short-circuit and return the desired state. + if !delta.DifferentExcept("Spec.Tags") { + return desired, nil + } + + input, err := rm.newUpdateRequestPayload(ctx, desired, delta) + if err != nil { + return nil, err + } + + var resp *svcsdk.UpdateWorkflowOutput + _ = resp + resp, err = rm.sdkapi.UpdateWorkflow(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "UpdateWorkflow", err) + if err != nil { + return nil, err + } + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := desired.ko.DeepCopy() + + if resp.ModifiedAt != nil { + ko.Status.ModifiedAt = &metav1.Time{*resp.ModifiedAt} + } else { + ko.Status.ModifiedAt = nil + } + if ko.Status.ACKResourceMetadata == nil { + ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + if resp.WorkflowArn != nil { + arn := ackv1alpha1.AWSResourceName(*resp.WorkflowArn) + ko.Status.ACKResourceMetadata.ARN = &arn + } + if resp.WorkflowVersion != nil { + ko.Status.WorkflowVersion = resp.WorkflowVersion + } else { + ko.Status.WorkflowVersion = nil + } + + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// newUpdateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Update API call for the resource +func (rm *resourceManager) newUpdateRequestPayload( + ctx context.Context, + r *resource, + delta *ackcompare.Delta, +) (*svcsdk.UpdateWorkflowInput, error) { + res := &svcsdk.UpdateWorkflowInput{} + + if r.ko.Spec.DefinitionS3Location != nil { + f0 := &svcsdktypes.DefinitionS3Location{} + if r.ko.Spec.DefinitionS3Location.Bucket != nil { + f0.Bucket = r.ko.Spec.DefinitionS3Location.Bucket + } + if r.ko.Spec.DefinitionS3Location.ObjectKey != nil { + f0.ObjectKey = r.ko.Spec.DefinitionS3Location.ObjectKey + } + if r.ko.Spec.DefinitionS3Location.VersionID != nil { + f0.VersionId = r.ko.Spec.DefinitionS3Location.VersionID + } + res.DefinitionS3Location = f0 + } + if r.ko.Spec.Description != nil { + res.Description = r.ko.Spec.Description + } + if r.ko.Spec.EngineVersion != nil { + engineVersionCopy0 := *r.ko.Spec.EngineVersion + if engineVersionCopy0 > math.MaxInt32 || engineVersionCopy0 < math.MinInt32 { + return nil, fmt.Errorf("error: field EngineVersion is of type int32") + } + engineVersionCopy := int32(engineVersionCopy0) + res.EngineVersion = engineVersionCopy + } + if r.ko.Spec.LoggingConfiguration != nil { + f3 := &svcsdktypes.LoggingConfiguration{} + if r.ko.Spec.LoggingConfiguration.LogGroupName != nil { + f3.LogGroupName = r.ko.Spec.LoggingConfiguration.LogGroupName + } + res.LoggingConfiguration = f3 + } + if r.ko.Spec.NetworkConfiguration != nil { + f4 := &svcsdktypes.NetworkConfiguration{} + if r.ko.Spec.NetworkConfiguration.SecurityGroupIDs != nil { + f4.SecurityGroupIds = aws.ToStringSlice(r.ko.Spec.NetworkConfiguration.SecurityGroupIDs) + } + if r.ko.Spec.NetworkConfiguration.SubnetIDs != nil { + f4.SubnetIds = aws.ToStringSlice(r.ko.Spec.NetworkConfiguration.SubnetIDs) + } + res.NetworkConfiguration = f4 + } + if r.ko.Spec.RoleARN != nil { + res.RoleArn = r.ko.Spec.RoleARN + } + if r.ko.Spec.TriggerMode != nil { + res.TriggerMode = r.ko.Spec.TriggerMode + } + if r.ko.Status.ACKResourceMetadata != nil && r.ko.Status.ACKResourceMetadata.ARN != nil { + res.WorkflowArn = (*string)(r.ko.Status.ACKResourceMetadata.ARN) + } + + return res, nil +} + +// sdkDelete deletes the supplied resource in the backend AWS service API +func (rm *resourceManager) sdkDelete( + ctx context.Context, + r *resource, +) (latest *resource, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.sdkDelete") + defer func() { + exit(err) + }() + input, err := rm.newDeleteRequestPayload(r) + if err != nil { + return nil, err + } + // DeleteWorkflow deletes only the specified version when WorkflowVersion is + // set; leaving it unset deletes the workflow entirely. ACK owns the whole + // workflow, so never scope the delete to a single version -- otherwise the + // API returns success, ACK removes the finalizer and reports the CR + // deleted, but the real workflow stays READY in AWS (resource leak). + input.WorkflowVersion = nil + + var resp *svcsdk.DeleteWorkflowOutput + _ = resp + resp, err = rm.sdkapi.DeleteWorkflow(ctx, input) + rm.metrics.RecordAPICall("DELETE", "DeleteWorkflow", err) + return nil, err +} + +// newDeleteRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Delete API call for the resource +func (rm *resourceManager) newDeleteRequestPayload( + r *resource, +) (*svcsdk.DeleteWorkflowInput, error) { + res := &svcsdk.DeleteWorkflowInput{} + + if r.ko.Status.ACKResourceMetadata != nil && r.ko.Status.ACKResourceMetadata.ARN != nil { + res.WorkflowArn = (*string)(r.ko.Status.ACKResourceMetadata.ARN) + } + if r.ko.Status.WorkflowVersion != nil { + res.WorkflowVersion = r.ko.Status.WorkflowVersion + } + + return res, nil +} + +// setStatusDefaults sets default properties into supplied custom resource +func (rm *resourceManager) setStatusDefaults( + ko *svcapitypes.Workflow, +) { + if ko.Status.ACKResourceMetadata == nil { + ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{} + } + if ko.Status.ACKResourceMetadata.Region == nil { + ko.Status.ACKResourceMetadata.Region = &rm.awsRegion + } + if ko.Status.ACKResourceMetadata.Partition == nil { + ko.Status.ACKResourceMetadata.Partition = &rm.awsPartition + } + if ko.Status.ACKResourceMetadata.OwnerAccountID == nil { + ko.Status.ACKResourceMetadata.OwnerAccountID = &rm.awsAccountID + } + if ko.Status.Conditions == nil { + ko.Status.Conditions = []*ackv1alpha1.Condition{} + } +} + +// updateConditions returns updated resource, true; if conditions were updated +// else it returns nil, false +func (rm *resourceManager) updateConditions( + r *resource, + onSuccess bool, + err error, +) (*resource, bool) { + ko := r.ko.DeepCopy() + rm.setStatusDefaults(ko) + + // Terminal condition + var terminalCondition *ackv1alpha1.Condition = nil + var recoverableCondition *ackv1alpha1.Condition = nil + var syncCondition *ackv1alpha1.Condition = nil + for _, condition := range ko.Status.Conditions { + if condition.Type == ackv1alpha1.ConditionTypeTerminal { + terminalCondition = condition + } + if condition.Type == ackv1alpha1.ConditionTypeRecoverable { + recoverableCondition = condition + } + if condition.Type == ackv1alpha1.ConditionTypeResourceSynced { + syncCondition = condition + } + } + var termError *ackerr.TerminalError + if rm.terminalAWSError(err) || err == ackerr.SecretTypeNotSupported || err == ackerr.SecretNotFound || errors.As(err, &termError) { + if terminalCondition == nil { + terminalCondition = &ackv1alpha1.Condition{ + Type: ackv1alpha1.ConditionTypeTerminal, + } + ko.Status.Conditions = append(ko.Status.Conditions, terminalCondition) + } + var errorMessage = "" + if err == ackerr.SecretTypeNotSupported || err == ackerr.SecretNotFound || errors.As(err, &termError) { + errorMessage = err.Error() + } else { + awsErr, _ := ackerr.AWSError(err) + errorMessage = awsErr.Error() + } + terminalCondition.Status = corev1.ConditionTrue + terminalCondition.Message = &errorMessage + } else { + // Clear the terminal condition if no longer present + if terminalCondition != nil { + terminalCondition.Status = corev1.ConditionFalse + terminalCondition.Message = nil + } + // Handling Recoverable Conditions + if err != nil { + if recoverableCondition == nil { + // Add a new Condition containing a non-terminal error + recoverableCondition = &ackv1alpha1.Condition{ + Type: ackv1alpha1.ConditionTypeRecoverable, + } + ko.Status.Conditions = append(ko.Status.Conditions, recoverableCondition) + } + recoverableCondition.Status = corev1.ConditionTrue + awsErr, _ := ackerr.AWSError(err) + errorMessage := err.Error() + if awsErr != nil { + errorMessage = awsErr.Error() + } + recoverableCondition.Message = &errorMessage + } else if recoverableCondition != nil { + recoverableCondition.Status = corev1.ConditionFalse + recoverableCondition.Message = nil + } + } + // Required to avoid the "declared but not used" error in the default case + _ = syncCondition + if terminalCondition != nil || recoverableCondition != nil || syncCondition != nil { + return &resource{ko}, true // updated + } + return nil, false // not updated +} + +// terminalAWSError returns awserr, true; if the supplied error is an aws Error type +// and if the exception indicates that it is a Terminal exception +// 'Terminal' exception are specified in generator configuration +func (rm *resourceManager) terminalAWSError(err error) bool { + if err == nil { + return false + } + + var terminalErr smithy.APIError + if !errors.As(err, &terminalErr) { + return false + } + switch terminalErr.ErrorCode() { + case "ValidationException": + return true + default: + return false + } +} diff --git a/pkg/resource/workflow/tags.go b/pkg/resource/workflow/tags.go new file mode 100644 index 0000000..0176bcf --- /dev/null +++ b/pkg/resource/workflow/tags.go @@ -0,0 +1,108 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Code generated by ack-generate. DO NOT EDIT. + +package workflow + +import ( + "slices" + "strings" + + acktags "github.com/aws-controllers-k8s/runtime/pkg/tags" + + svcapitypes "github.com/aws-controllers-k8s/mwaaserverless-controller/apis/v1alpha1" +) + +var ( + _ = svcapitypes.Workflow{} + _ = acktags.NewTags() +) + +// convertToOrderedACKTags converts the tags parameter into 'acktags.Tags' shape. +// This method helps in creating the hub(acktags.Tags) for merging +// default controller tags with existing resource tags. It also returns a slice +// of keys maintaining the original key Order when the tags are a list +func convertToOrderedACKTags(tags map[string]*string) (acktags.Tags, []string) { + result := acktags.NewTags() + keyOrder := []string{} + + if len(tags) == 0 { + return result, keyOrder + } + for k, v := range tags { + if v == nil { + result[k] = "" + } else { + result[k] = *v + } + } + + return result, keyOrder +} + +// fromACKTags converts the tags parameter into map[string]*string shape. +// This method helps in setting the tags back inside AWSResource after merging +// default controller tags with existing resource tags. When a list, +// it maintains the order from original +func fromACKTags(tags acktags.Tags, keyOrder []string) map[string]*string { + result := map[string]*string{} + + _ = keyOrder + for k, v := range tags { + result[k] = &v + } + + return result +} + +// ignoreSystemTags ignores tags that have keys that start with "aws:" +// and systemTags defined on startup via the --resource-tags flag, +// to avoid patching them to the resourceSpec. +// Eg. resources created with cloudformation have tags that cannot be +// removed by an ACK controller +func ignoreSystemTags(tags acktags.Tags, systemTags []string) { + for k := range tags { + if strings.HasPrefix(k, "aws:") || + slices.Contains(systemTags, k) { + delete(tags, k) + } + } +} + +// syncAWSTags ensures AWS-managed tags (prefixed with "aws:") from the latest resource state +// are preserved in the desired state. This prevents the controller from attempting to +// modify AWS-managed tags, which would result in an error. +// +// AWS-managed tags are automatically added by AWS services (e.g., CloudFormation, Service Catalog) +// and cannot be modified or deleted through normal tag operations. Common examples include: +// - aws:cloudformation:stack-name +// - aws:servicecatalog:productArn +// +// Parameters: +// - a: The target Tags map to be updated (typically desired state) +// - b: The source Tags map containing AWS-managed tags (typically latest state) +// +// Example: +// +// latest := Tags{"aws:cloudformation:stack-name": "my-stack", "environment": "prod"} +// desired := Tags{"environment": "dev"} +// SyncAWSTags(desired, latest) +// desired now contains {"aws:cloudformation:stack-name": "my-stack", "environment": "dev"} +func syncAWSTags(a acktags.Tags, b acktags.Tags) { + for k := range b { + if strings.HasPrefix(k, "aws:") { + a[k] = b[k] + } + } +} diff --git a/pkg/tags/sync.go b/pkg/tags/sync.go new file mode 100644 index 0000000..ca7de36 --- /dev/null +++ b/pkg/tags/sync.go @@ -0,0 +1,116 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Package tags contains reusable helpers for keeping a Workflow's AWS tags in +// sync with its desired state. The MWAA Serverless API exposes tags only via +// the separate TagResource/UntagResource/ListTagsForResource operations (there +// is no inline Tags member on Create/Get/Update), so tag reconciliation is +// implemented here rather than handled declaratively by the code-generator. +package tags + +import ( + "context" + + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + + svcsdk "github.com/aws/aws-sdk-go-v2/service/mwaaserverless" +) + +type metricsRecorder interface { + RecordAPICall(opType string, opID string, err error) +} + +type tagsClient interface { + TagResource(context.Context, *svcsdk.TagResourceInput, ...func(*svcsdk.Options)) (*svcsdk.TagResourceOutput, error) + UntagResource(context.Context, *svcsdk.UntagResourceInput, ...func(*svcsdk.Options)) (*svcsdk.UntagResourceOutput, error) + ListTagsForResource(context.Context, *svcsdk.ListTagsForResourceInput, ...func(*svcsdk.Options)) (*svcsdk.ListTagsForResourceOutput, error) +} + +// GetTags returns the tags associated with the resource identified by the +// supplied ARN. GetWorkflow does not return tags inline, so the read path +// calls this to populate Spec.Tags. +func GetTags( + ctx context.Context, + client tagsClient, + mr metricsRecorder, + resourceARN string, +) (tags map[string]string, err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("tags.GetTags") + defer func() { exit(err) }() + + input := &svcsdk.ListTagsForResourceInput{ + ResourceArn: &resourceARN, + } + resp, err := client.ListTagsForResource(ctx, input) + mr.RecordAPICall("GET", "ListTagsForResource", err) + if err != nil { + return nil, err + } + return resp.Tags, nil +} + +// SyncTags calls TagResource and UntagResource to ensure the set of associated +// tags stays in sync with the desired state. +func SyncTags( + ctx context.Context, + client tagsClient, + mr metricsRecorder, + resourceARN string, + desiredTags map[string]string, + existingTags map[string]string, +) (err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("tags.SyncTags") + defer func() { exit(err) }() + + toAdd := map[string]string{} + toDelete := []string{} + + for k, v := range desiredTags { + if ev, found := existingTags[k]; !found || ev != v { + toAdd[k] = v + } + } + for k := range existingTags { + if _, found := desiredTags[k]; !found { + toDelete = append(toDelete, k) + } + } + + if len(toDelete) > 0 { + input := &svcsdk.UntagResourceInput{ + ResourceArn: &resourceARN, + TagKeys: toDelete, + } + _, err = client.UntagResource(ctx, input) + mr.RecordAPICall("UPDATE", "UntagResource", err) + if err != nil { + return err + } + } + + if len(toAdd) > 0 { + input := &svcsdk.TagResourceInput{ + ResourceArn: &resourceARN, + Tags: toAdd, + } + _, err = client.TagResource(ctx, input) + mr.RecordAPICall("UPDATE", "TagResource", err) + if err != nil { + return err + } + } + + return nil +} diff --git a/templates/hooks/workflow/sdk_delete_post_build_request.go.tpl b/templates/hooks/workflow/sdk_delete_post_build_request.go.tpl new file mode 100644 index 0000000..b21cd98 --- /dev/null +++ b/templates/hooks/workflow/sdk_delete_post_build_request.go.tpl @@ -0,0 +1,6 @@ + // DeleteWorkflow deletes only the specified version when WorkflowVersion is + // set; leaving it unset deletes the workflow entirely. ACK owns the whole + // workflow, so never scope the delete to a single version -- otherwise the + // API returns success, ACK removes the finalizer and reports the CR + // deleted, but the real workflow stays READY in AWS (resource leak). + input.WorkflowVersion = nil diff --git a/templates/hooks/workflow/sdk_read_one_post_set_output.go.tpl b/templates/hooks/workflow/sdk_read_one_post_set_output.go.tpl new file mode 100644 index 0000000..0bcf9ad --- /dev/null +++ b/templates/hooks/workflow/sdk_read_one_post_set_output.go.tpl @@ -0,0 +1,18 @@ + if ko.Status.ACKResourceMetadata != nil && ko.Status.ACKResourceMetadata.ARN != nil { + tags, err := rm.getTags(ctx, string(*ko.Status.ACKResourceMetadata.ARN)) + if err != nil { + return nil, err + } + ko.Spec.Tags = tags + } + + // GetWorkflow never returns EngineVersion, so the generated read fabricates + // EngineVersion=0. Treat that fabricated zero as "not observed" and mirror + // the desired value onto the read: an unset field stays absent (no &0 + // poisoning the CR spec / no UpdateWorkflow sending EngineVersion=0), and a + // set field round-trips cleanly with no phantom delta. If MWAA ever starts + // echoing a real (non-zero) EngineVersion the normalization steps aside and + // the generated comparator detects genuine drift. + if ko.Spec.EngineVersion == nil || *ko.Spec.EngineVersion == 0 { + ko.Spec.EngineVersion = r.ko.Spec.EngineVersion + } diff --git a/templates/hooks/workflow/sdk_update_pre_build_request.go.tpl b/templates/hooks/workflow/sdk_update_pre_build_request.go.tpl new file mode 100644 index 0000000..229c2ae --- /dev/null +++ b/templates/hooks/workflow/sdk_update_pre_build_request.go.tpl @@ -0,0 +1,12 @@ + // Tags are managed via TagResource/UntagResource, not UpdateWorkflow. Sync + // any tag drift here so the resource doesn't reconcile forever. + if delta.DifferentAt("Spec.Tags") { + if err = rm.syncTags(ctx, desired, latest); err != nil { + return nil, err + } + } + // If ONLY tags changed there is nothing for UpdateWorkflow to do, so + // short-circuit and return the desired state. + if !delta.DifferentExcept("Spec.Tags") { + return desired, nil + } diff --git a/test/e2e/resources/workflow-no-engine-version.yaml b/test/e2e/resources/workflow-no-engine-version.yaml new file mode 100644 index 0000000..f5e7285 --- /dev/null +++ b/test/e2e/resources/workflow-no-engine-version.yaml @@ -0,0 +1,15 @@ +apiVersion: mwaaserverless.services.k8s.aws/v1alpha1 +kind: Workflow +metadata: + name: $WORKFLOW_NAME +spec: + name: $WORKFLOW_NAME + roleARN: $EXECUTION_ROLE_ARN + # engineVersion intentionally OMITTED: exercises the read path that fabricates + # EngineVersion=0 to prove it stays absent (no &0 poisoning, no phantom update). + definitionS3Location: + bucket: $DEFINITION_BUCKET + objectKey: $DEFINITION_OBJECT_KEY + description: ACK e2e test workflow (no engineVersion) + # "scheduled" would need a schedule expression, which the spec can't set. + triggerMode: manual_only diff --git a/test/e2e/resources/workflow.yaml b/test/e2e/resources/workflow.yaml new file mode 100644 index 0000000..7899fa1 --- /dev/null +++ b/test/e2e/resources/workflow.yaml @@ -0,0 +1,14 @@ +apiVersion: mwaaserverless.services.k8s.aws/v1alpha1 +kind: Workflow +metadata: + name: $WORKFLOW_NAME +spec: + name: $WORKFLOW_NAME + roleARN: $EXECUTION_ROLE_ARN + engineVersion: 1 + definitionS3Location: + bucket: $DEFINITION_BUCKET + objectKey: $DEFINITION_OBJECT_KEY + description: ACK e2e test workflow + # "scheduled" would need a schedule expression, which the spec can't set. + triggerMode: manual_only diff --git a/test/e2e/service_bootstrap.py b/test/e2e/service_bootstrap.py index 39dbc7a..a9c3e6f 100644 --- a/test/e2e/service_bootstrap.py +++ b/test/e2e/service_bootstrap.py @@ -37,29 +37,20 @@ # S3 object key under which the workflow definition YAML is uploaded. DEFINITION_OBJECT_KEY = "workflows/example.yaml" -# Minimal MWAA Serverless workflow definition (a YAML DAG). +# Minimal MWAA Serverless workflow definition (a dag-factory-style YAML DAG). # -# GUESS / VERIFY: The exact schema MWAA Serverless expects for the definition -# body is not yet pinned down from the public docs. MWAA Serverless workflow -# definitions are YAML DAGs (NOT Python) that describe tasks/operators and -# their dependencies. The body below is a documented-minimal example shaped -# like an Airflow-style YAML DAG; it MUST be reconciled with the real MWAA -# Serverless definition schema during the first real e2e run, or CreateWorkflow -# may reject it with a ValidationException. +# MWAA Serverless workflow definitions are YAML DAGs (NOT Python) in the +# dag-factory format: a top-level key mapping to a `tasks` mapping +# (task name -> operator/dependencies). This body was validated against the +# live MWAA Serverless CreateWorkflow API. WORKFLOW_DEFINITION_BODY = """\ -# Minimal MWAA Serverless workflow definition (YAML DAG). -# VERIFY this matches the MWAA Serverless definition schema on first real run. -dag: - dag_id: ack_example_workflow - schedule: null +ack_example_workflow: + default_args: { owner: airflow } + schedule_interval: null catchup: false tasks: - - task_id: start - operator: airflow.operators.empty.EmptyOperator - - task_id: finish - operator: airflow.operators.empty.EmptyOperator - dependencies: - - start + start: { operator: airflow.operators.empty.EmptyOperator } + finish: { operator: airflow.operators.empty.EmptyOperator, dependencies: [start] } """ diff --git a/test/e2e/tests/test_workflow.py b/test/e2e/tests/test_workflow.py new file mode 100644 index 0000000..683e2fd --- /dev/null +++ b/test/e2e/tests/test_workflow.py @@ -0,0 +1,354 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You may +# not use this file except in compliance with the License. A copy of the +# License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""Integration tests for the MWAA Serverless Workflow resource. + +MWAA Serverless workflow create/update/delete are fast (the full lifecycle +runs in ~4 min in e2e), so a single modest timeout ceiling covers every op. + +Each test provisions a uniquely-named workflow and cleans it up, so they are +safe to run concurrently under pytest-xdist (`-n auto`). + +Marked @pytest.mark.canary so the kind-e2e canary runs them. +""" + +import datetime +import logging +import time + +import pytest + +from acktest.k8s import resource as k8s +from acktest.k8s import condition +from acktest.resources import random_suffix_name +from e2e import service_marker, CRD_GROUP, CRD_VERSION, load_mwaaserverless_resource +from e2e.replacement_values import REPLACEMENT_VALUES + +RESOURCE_PLURAL = "workflows" + +# A single ceiling for every workflow lifecycle op (create/update/delete). The +# full create->update->delete cycle completes in ~4 min in e2e, so 8 min gives +# generous headroom for any one op without masking a genuinely stuck reconcile. +TIMEOUT_SECONDS = 60 * 8 + +POLL_INTERVAL_SECONDS = 30 + +# Time to let the controller reconcile after a k8s operation +RECONCILE_WAIT_SECONDS = 30 + +# READY is the synced/available state for an MWAA Serverless workflow; DELETING +# is the transient state while it is being torn down. +WORKFLOW_STATUS_READY = "READY" + + +def wait_for_cr_status(ref, target_status, timeout_seconds): + """Poll the CR until status.status matches target_status.""" + deadline = datetime.datetime.now() + datetime.timedelta(seconds=timeout_seconds) + while datetime.datetime.now() < deadline: + try: + cr = k8s.get_resource(ref) + status = cr.get("status", {}).get("status") + logging.info(f"CR {ref.name} status: {status}") + if status == target_status: + return cr + except Exception as e: + logging.warning(f"Transient error getting CR {ref.name}: {e}") + time.sleep(POLL_INTERVAL_SECONDS) + pytest.fail(f"Timed out waiting for CR status to reach {target_status}") + + +def get_workflow_arn(ref): + """Read the server-assigned WorkflowArn from the CR's ackResourceMetadata. + + GetWorkflow/ListTagsForResource are keyed by the WorkflowArn (not the + workflow Name), so the ARN must be read from + status.ackResourceMetadata.arn. + """ + cr = k8s.get_resource(ref) + arn = (cr or {}).get("status", {}).get("ackResourceMetadata", {}).get("arn") + assert arn, "status.ackResourceMetadata.arn should be set on the CR" + return arn + + +def wait_for_workflow_status(mwaaserverless_client, workflow_arn, target_status, + timeout_seconds, cr_ref=None): + """Poll the MWAA Serverless API until the workflow reaches target_status. + + If ``cr_ref`` is provided, also inspect the CR's conditions on each + iteration and fast-fail if the controller has set ACK.Terminal=True. + Without this, a terminal error surfaced only on the CR (e.g. a + ValidationException that prevents the CreateWorkflow call from ever + reaching AWS) would be invisible to this poll loop. Pass ``cr_ref=None`` + when polling for deletion because the CR is intentionally being torn down. + """ + deadline = datetime.datetime.now() + datetime.timedelta(seconds=timeout_seconds) + while datetime.datetime.now() < deadline: + # Fail fast if the controller marked the CR terminal. + if cr_ref is not None: + try: + cr = k8s.get_resource(cr_ref) + for cond in (cr or {}).get("status", {}).get("conditions", []) or []: + if (cond.get("type") == condition.CONDITION_TYPE_TERMINAL + and cond.get("status") == "True"): + pytest.fail( + f"Controller set {condition.CONDITION_TYPE_TERMINAL} on " + f"{cr_ref.name}: {cond.get('message')}" + ) + except Exception as e: + # CR read failure is transient; keep polling. + logging.warning(f"Transient error reading CR {cr_ref.name}: {e}") + + try: + resp = mwaaserverless_client.get_workflow(WorkflowArn=workflow_arn) + # GetWorkflow returns the status under "WorkflowStatus", not "Status" + # (the GetWorkflowResponse member is workflow_status/WorkflowStatus). + status = resp.get("WorkflowStatus") + logging.info(f"Workflow {workflow_arn}: {status}") + if status == target_status: + return status + except mwaaserverless_client.exceptions.ResourceNotFoundException: + if target_status == "DELETED": + return "DELETED" + # Controller may not have issued CreateWorkflow yet, or the API is + # eventually consistent. Keep polling until timeout. + logging.info( + f"Workflow {workflow_arn} not yet visible in AWS; continuing to poll" + ) + time.sleep(POLL_INTERVAL_SECONDS) + pytest.fail(f"Timed out waiting for workflow {workflow_arn} to reach {target_status}") + + +@service_marker +@pytest.mark.canary +def test_workflow_lifecycle(mwaaserverless_client): + """End-to-end create -> update -> delete for an MWAA Serverless Workflow.""" + workflow_name = random_suffix_name("ack-mwaaserverless", 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements["WORKFLOW_NAME"] = workflow_name + + resource_data = load_mwaaserverless_resource( + "workflow", + additional_replacements=replacements, + ) + logging.debug(resource_data) + + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, + workflow_name, namespace="default", + ) + + # --- Create --- + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref) + assert cr is not None + assert k8s.get_resource_exists(ref) + + try: + # Wait for READY on the CR first; the ARN is only available once the + # controller has created the resource and populated status. + cr = wait_for_cr_status(ref, WORKFLOW_STATUS_READY, TIMEOUT_SECONDS) + + workflow_arn = get_workflow_arn(ref) + + # Verify the workflow is READY in AWS, keyed by ARN. + wait_for_workflow_status( + mwaaserverless_client, workflow_arn, WORKFLOW_STATUS_READY, + TIMEOUT_SECONDS, cr_ref=ref, + ) + + # Verify Synced condition. + assert k8s.wait_on_condition( + ref, condition.CONDITION_TYPE_RESOURCE_SYNCED, "True", wait_periods=10, + ) + + # Verify status fields populated on the CR. + assert "arn" in cr["status"]["ackResourceMetadata"], \ + "ackResourceMetadata.arn should be set after READY" + assert "version" in cr["status"], \ + "version should be set after READY" + assert "createdAt" in cr["status"], \ + "createdAt should be set after READY" + + # --- No-op stability --- + # With no spec change, the controller must NOT issue an UpdateWorkflow. + # A read-path that writes a server-defaulted/omitted field back onto a + # nil Spec field would create phantom drift, and because UpdateWorkflow + # mints a new WorkflowVersion, that would show up as the version + # advancing on every resync with no user change. Capture the version + # after the initial READY, let the controller run several resync + # periods, and assert it is unchanged. + initial_version = cr["status"]["version"] + time.sleep(RECONCILE_WAIT_SECONDS * 3) + stable_cr = k8s.get_resource(ref) + assert stable_cr["status"]["version"] == initial_version, ( + "version advanced with no spec change: " + f"{initial_version} -> {stable_cr['status']['version']}; " + "the read path is manufacturing phantom drift and churning " + "UpdateWorkflow" + ) + + # --- Update --- + # Patch the description and tags to exercise both the update path and + # the tag-sync path (TagResource/UntagResource). + new_description = "ACK e2e test workflow (updated)" + new_tags = {"ack-e2e": "updated"} + updates = { + "spec": { + "description": new_description, + "tags": new_tags, + }, + } + k8s.patch_custom_resource(ref, updates) + time.sleep(RECONCILE_WAIT_SECONDS) + + # Wait for the workflow to settle back to READY after the update. + wait_for_workflow_status( + mwaaserverless_client, workflow_arn, WORKFLOW_STATUS_READY, + TIMEOUT_SECONDS, cr_ref=ref, + ) + wait_for_cr_status(ref, WORKFLOW_STATUS_READY, TIMEOUT_SECONDS) + + # Verify Synced after update. + assert k8s.wait_on_condition( + ref, condition.CONDITION_TYPE_RESOURCE_SYNCED, "True", wait_periods=10, + ) + + # Verify the description changed in AWS (GetWorkflow is ARN-keyed). + # GetWorkflow returns the description at resp["Description"]. + aws_res = mwaaserverless_client.get_workflow(WorkflowArn=workflow_arn) + assert aws_res.get("Description") == new_description + + # Verify the tags changed in AWS. GetWorkflow does NOT return tags + # inline, so list_tags_for_resource (ARN-keyed) is used; it returns the + # tags at resp["Tags"] as a flat key->value map. + tags_res = mwaaserverless_client.list_tags_for_resource(ResourceArn=workflow_arn) + assert tags_res.get("Tags", {}).get("ack-e2e") == "updated" + + finally: + # --- Delete (teardown) --- + # Best-effort: don't let a teardown error mask a test-body failure, but + # still log it and wait for the workflow to actually disappear so we + # don't leak AWS resources. + try: + workflow_arn = get_workflow_arn(ref) + except Exception: + workflow_arn = None + try: + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + if not deleted: + logging.warning( + f"delete_custom_resource returned False for {workflow_name}" + ) + if workflow_arn is not None: + # Verify the workflow is gone from AWS (GetWorkflow eventually + # raises ResourceNotFoundException). + wait_for_workflow_status( + mwaaserverless_client, workflow_arn, "DELETED", + TIMEOUT_SECONDS, + ) + except Exception as e: + logging.error(f"Teardown failed for {workflow_name}: {e}") + + +@service_marker +@pytest.mark.canary +def test_workflow_unset_engine_version(mwaaserverless_client): + """Create -> resync -> delete a Workflow whose spec OMITS engineVersion. + + GetWorkflow never returns EngineVersion, so the generated read path + fabricates Spec.EngineVersion=0 for the unset case. The read hook now + unconditionally mirrors the desired Spec (including nil) onto the observed + read. This asserts: + * spec.engineVersion stays absent/nil after READY (NOT 0) -- the &0 + poisoning that would drive UpdateWorkflow to send EngineVersion=0 and + hit ValidationException never happens; and + * version is stable across a resync -- no phantom drift/churn. + """ + workflow_name = random_suffix_name("ack-mwaaserverless-noev", 32) + + replacements = REPLACEMENT_VALUES.copy() + replacements["WORKFLOW_NAME"] = workflow_name + + resource_data = load_mwaaserverless_resource( + "workflow-no-engine-version", + additional_replacements=replacements, + ) + logging.debug(resource_data) + + ref = k8s.CustomResourceReference( + CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL, + workflow_name, namespace="default", + ) + + k8s.create_custom_resource(ref, resource_data) + cr = k8s.wait_resource_consumed_by_controller(ref) + assert cr is not None + assert k8s.get_resource_exists(ref) + + try: + cr = wait_for_cr_status(ref, WORKFLOW_STATUS_READY, TIMEOUT_SECONDS) + + workflow_arn = get_workflow_arn(ref) + wait_for_workflow_status( + mwaaserverless_client, workflow_arn, WORKFLOW_STATUS_READY, + TIMEOUT_SECONDS, cr_ref=ref, + ) + assert k8s.wait_on_condition( + ref, condition.CONDITION_TYPE_RESOURCE_SYNCED, "True", wait_periods=10, + ) + + # engineVersion was omitted from the spec; it must NOT be persisted as 0 + # (or any value) by late_initialize / the read path. + engine_version = cr.get("spec", {}).get("engineVersion") + assert engine_version is None, ( + "spec.engineVersion should stay absent when omitted, but was " + f"persisted as {engine_version!r}; the read path is poisoning the " + "spec with a fabricated value" + ) + + # No spec change -> version must be stable across resyncs. A phantom + # EngineVersion delta would drive UpdateWorkflow (minting a new version) + # and, worse, send EngineVersion=0 -> ValidationException. + initial_version = cr["status"]["version"] + time.sleep(RECONCILE_WAIT_SECONDS * 3) + stable_cr = k8s.get_resource(ref) + assert stable_cr.get("spec", {}).get("engineVersion") is None, ( + "spec.engineVersion appeared after a resync despite being omitted; " + "the read path is manufacturing a fabricated EngineVersion" + ) + assert stable_cr["status"]["version"] == initial_version, ( + "version advanced with no spec change on the unset-" + f"engineVersion workflow: {initial_version} -> " + f"{stable_cr['status']['version']}; the read path is " + "churning UpdateWorkflow via a phantom EngineVersion delta" + ) + + finally: + try: + workflow_arn = get_workflow_arn(ref) + except Exception: + workflow_arn = None + try: + _, deleted = k8s.delete_custom_resource(ref, 3, 10) + if not deleted: + logging.warning( + f"delete_custom_resource returned False for {workflow_name}" + ) + if workflow_arn is not None: + wait_for_workflow_status( + mwaaserverless_client, workflow_arn, "DELETED", + TIMEOUT_SECONDS, + ) + except Exception as e: + logging.error(f"Teardown failed for {workflow_name}: {e}")