From f376743f9768c58bda77d373a2b9aab60f1b9834 Mon Sep 17 00:00:00 2001 From: Ramon Quitales Date: Mon, 23 Sep 2024 15:49:01 -0700 Subject: [PATCH] Implement manager.Runnable for fs --- operator/cmd/main.go | 58 +++++++---- operator/cmd/main_test.go | 96 +++++++++++++++++++ .../config/crd/bases/pulumi.com_programs.yaml | 12 +-- 3 files changed, 144 insertions(+), 22 deletions(-) create mode 100644 operator/cmd/main_test.go diff --git a/operator/cmd/main.go b/operator/cmd/main.go index e2e17c42..13dec66e 100644 --- a/operator/cmd/main.go +++ b/operator/cmd/main.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "context" "crypto/tls" "flag" "net" @@ -142,15 +143,9 @@ func main() { os.Exit(1) } - // Create a new ProgramHandler to handle Program objects. + // Create a new ProgramHandler to handle Program objects. Both the ProgramReconciler and the file server need to + // access the ProgramHandler, so it is created here and passed to both. pHandler := newProgramHandler(mgr.GetClient(), programFSAdvAddr) - // Start a simple file-server to serve CR files as compressed tarballs. - go func() { - // Wait until our manager is elected leader before starting the file server. - <-mgr.Elected() - - startProgramFileServer(pHandler, programFSAddr) - }() if err = (&autocontroller.WorkspaceReconciler{ Client: mgr.GetClient(), @@ -196,6 +191,16 @@ func main() { os.Exit(1) } + // Start the file server for serving Program objects. + setupLog.Info("starting file server for program resource", + "address", programFSAddr, + "advertisedAddress", programFSAdvAddr, + ) + if err := mgr.Add(pFileserver{pHandler, programFSAddr}); err != nil { + setupLog.Error(err, "unable to start file server for program resource") + os.Exit(1) + } + setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") @@ -203,15 +208,36 @@ func main() { } } -// startProgramFileServer starts a simple file server to serve Program objects as compressed tarballs. This allows -// children pods with restricted permissions to access the Program objects, without needing read permissions granted. -func startProgramFileServer(programHandler *pulumicontroller.ProgramHandler, address string) { - setupLog.Info("starting file server to serve Program objects", "address", address, "advertisedAddress", programHandler.Address()) +// pFileserver implements the manager.Runnable interface to start a simple file server to serve Program objects as +// compressed tarballs. +type pFileserver struct { + handler *pulumicontroller.ProgramHandler + address string +} + +// Start starts the file server to serve Program objects as compressed tarballs. +func (fs pFileserver) Start(ctx context.Context) error { mux := http.NewServeMux() - mux.Handle("/programs/", programHandler.HandleProgramServing()) - err := http.ListenAndServe(address, mux) - if err != nil { - setupLog.Error(err, "Program file server error") + mux.Handle("/programs/", fs.handler.HandleProgramServing()) + + server := &http.Server{ + Addr: fs.address, + Handler: mux, + } + + errChan := make(chan error) + go func() { + err := server.ListenAndServe() + if err != nil { + errChan <- err + } + }() + + select { + case err := <-errChan: + return err + case <-ctx.Done(): + return server.Shutdown(ctx) } } diff --git a/operator/cmd/main_test.go b/operator/cmd/main_test.go new file mode 100644 index 00000000..814345e6 --- /dev/null +++ b/operator/cmd/main_test.go @@ -0,0 +1,96 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License 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 main + +import ( + "testing" + + _ "k8s.io/client-go/plugin/pkg/client/auth" +) + +func TestDetermineAdvAddr(t *testing.T) { + const fakehostname = "fakehostname" + t.Setenv("HOSTNAME", fakehostname) + + tests := []struct { + addr string + want string + }{ + { + addr: ":9090", + want: "localhost:9090", + }, + { + addr: "localhost:1111", + want: "localhost:1111", + }, + { + addr: "0.0.0.0:9090", + want: fakehostname + ":9090", + }, + { + addr: "fake.default.svc.cluster.local:9090", + want: "fake.default.svc.cluster.local:9090", + }, + } + for _, tc := range tests { + t.Run(tc.addr, func(t *testing.T) { + if got := determineAdvAddr(tc.addr); got != tc.want { + t.Errorf("determineAdvAddr() = %v, want %v", got, tc.want) + } + }) + } +} + +func TestEnvOrDefault(t *testing.T) { + // Set up some ENV vars for testing. + t.Setenv("TEST_ENV", "test") + t.Setenv("EMPTY_ENV", "") + + tests := []struct { + name string + envName string + defaultValue string + want string + }{ + { + name: "env set, default ignored", + envName: "TEST_ENV", + defaultValue: "default", + want: "test", + }, + { + name: "env not set, default used", + envName: "EMPTY_ENV", + defaultValue: "default", + want: "default", + }, + { + name: "env not set, no default", + envName: "EMPTY_ENV", + defaultValue: "", + want: "", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if got := envOrDefault(tc.envName, tc.defaultValue); got != tc.want { + t.Errorf("envOrDefault() = %v, want %v", got, tc.want) + } + }) + } +} diff --git a/operator/config/crd/bases/pulumi.com_programs.yaml b/operator/config/crd/bases/pulumi.com_programs.yaml index 87072135..5982e113 100644 --- a/operator/config/crd/bases/pulumi.com_programs.yaml +++ b/operator/config/crd/bases/pulumi.com_programs.yaml @@ -18,8 +18,8 @@ spec: - jsonPath: .metadata.creationTimestamp name: Age type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status + - jsonPath: .status.artifact.url + name: URL type: string name: v1 schema: @@ -206,16 +206,16 @@ spec: type: string url: description: |- - URL is the HTTP address of this artifact that is served by the controller. It - allows other pods to access the contents of the artifact without needing the correct - RBAC policies. + URL is the HTTP address of the artifact as exposed by the controller + managing the source spec. It can be used to retrieve the artifact for + consumption, e.g. by another controller applying the artifact contents. type: string required: - url type: object lastResyncTime: description: LastResyncTime contains a timestamp for the last time - a resync of the stack took place. + a resync of the Program took place. format: date-time type: string observedGeneration: