github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/test/integration/cmd/fakepubsub/main.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // fakepubsub wraps around the official gcloud Pub/Sub emulator. 18 package main 19 20 import ( 21 "context" 22 "flag" 23 "fmt" 24 "os" 25 "os/exec" 26 "strings" 27 "syscall" 28 29 "github.com/sirupsen/logrus" 30 31 "github.com/zppinho/prow/test/integration/internal/fakepubsub" 32 configflagutil "sigs.k8s.io/prow/pkg/flagutil/config" 33 "sigs.k8s.io/prow/pkg/interrupts" 34 "sigs.k8s.io/prow/pkg/logrusutil" 35 "sigs.k8s.io/prow/pkg/pjutil" 36 ) 37 38 type options struct { 39 // config is the Prow configuration. We need this to read in the 40 // 'pubsub_subscriptions' field set in the integration test's Prow 41 // configuration, because we have to initialize (create) these subscriptions 42 // before sub can start listening to them. 43 config configflagutil.ConfigOptions 44 emulatorHostPort string 45 } 46 47 func (o *options) validate() error { 48 return nil 49 } 50 51 func flagOptions() *options { 52 // When the KIND cluster starts, the Prow configs get loaded into a 53 // Kubernetes ConfigMap object. This object is then mounted as a volume into 54 // the fakepubsub container at the path "/etc/config/config.yaml". 55 o := &options{config: configflagutil.ConfigOptions{ConfigPath: "/etc/config/config.yaml"}} 56 fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 57 58 fs.StringVar(&o.emulatorHostPort, "emulator-host-port", "0.0.0.0:8085", "Host and port of the running Pub/Sub emulator.") 59 o.config.AddFlags(fs) 60 61 fs.Parse(os.Args[1:]) 62 63 return o 64 } 65 66 func main() { 67 logrusutil.ComponentInit() 68 69 o := flagOptions() 70 if err := o.validate(); err != nil { 71 logrus.WithError(err).Fatal("Invalid arguments.") 72 } 73 74 health := pjutil.NewHealth() 75 health.ServeReady() 76 77 defer interrupts.WaitForGracefulShutdown() 78 79 if err := startPubSubEmulator(o); err != nil { 80 logrus.WithError(err).Fatal("could not start Pub/Sub emulator") 81 } 82 83 if err := initEmulatorState(o); err != nil { 84 logrus.WithError(err).Fatal("Could not initialize emulator state") 85 } 86 } 87 88 // startPubSubEmulator starts the Pub/Sub Emulator. It's a Java server, so the 89 // host system needs the JRE installed as well as gcloud cli (this the 90 // recommended way to start the emulator). 91 func startPubSubEmulator(o *options) error { 92 logrus.Info("Starting Pub/Sub emulator...") 93 94 args := []string{"beta", "emulators", "pubsub", "start", 95 fmt.Sprintf("--host-port=%s", o.emulatorHostPort)} 96 cmd := exec.Command("gcloud", args...) 97 98 // Unfortunately the emulator does not really give useful messages about 99 // what type of gRPC request is being served. Still, this is better than 100 // nothing. 101 cmd.Stdout = os.Stdout 102 cmd.Stderr = os.Stderr 103 104 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 105 if err := cmd.Start(); err != nil { 106 return fmt.Errorf("Could not start process: %v", err) 107 } 108 logrus.Info("Started Pub/Sub emulator") 109 110 pgid, err := syscall.Getpgid(cmd.Process.Pid) 111 if err != nil { 112 return fmt.Errorf("Could not get pid: %v", err) 113 } 114 115 // Cleanup. Kill child processes (in our case, the emulator) if we detect 116 // that we're getting shut down. See 117 // https://stackoverflow.com/a/29552044/437583. 118 interrupts.Run(func(ctx context.Context) { 119 for { 120 if _, ok := <-ctx.Done(); ok { 121 syscall.Kill(-pgid, syscall.SIGTERM) 122 cmd.Wait() 123 logrus.Info("Pub/Sub emulator exited.") 124 return 125 } 126 } 127 }) 128 129 return nil 130 } 131 132 // initEmulatorState creates Pub/Sub topics and subscriptions, because 133 // every time the emulator starts, it starts off from a clean slate (no topics 134 // or subscriptions). 135 func initEmulatorState(o *options) error { 136 configAgent, err := o.config.ConfigAgent() 137 if err != nil { 138 return fmt.Errorf("Error starting config agent: %v", err) 139 } 140 141 subs := configAgent.Config().PubSubSubscriptions 142 143 logrus.Info("Initializing Pub/Sub emulator state...") 144 145 ctx := context.Background() 146 147 for projectID, subscriptionIDs := range subs { 148 client, err := fakepubsub.NewClient(projectID, o.emulatorHostPort) 149 if err != nil { 150 return err 151 } 152 for _, subscriptionID := range subscriptionIDs { 153 // Extract the number part from the subscriptionID. The pattern we use 154 // for tests is "subscriptionN" where the trailing N is a number. 155 // Example: For "subscription1", we create "topic1". 156 numberPart := strings.TrimPrefix(subscriptionID, "subscription") 157 topicID := "topic" + numberPart 158 if err := client.CreateSubscription(ctx, projectID, topicID, subscriptionID); err != nil { 159 return err 160 } 161 } 162 } 163 164 return nil 165 }