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  }