• United States+1
  • United Kingdom+44
  • Afghanistan (‫افغانستان‬‎)+93
  • Albania (Shqipëri)+355
  • Algeria (‫الجزائر‬‎)+213
  • American Samoa+1684
  • Andorra+376
  • Angola+244
  • Anguilla+1264
  • Antigua and Barbuda+1268
  • Argentina+54
  • Armenia (Հայաստան)+374
  • Aruba+297
  • Australia+61
  • Austria (Österreich)+43
  • Azerbaijan (Azərbaycan)+994
  • Bahamas+1242
  • Bahrain (‫البحرين‬‎)+973
  • Bangladesh (বাংলাদেশ)+880
  • Barbados+1246
  • Belarus (Беларусь)+375
  • Belgium (België)+32
  • Belize+501
  • Benin (Bénin)+229
  • Bermuda+1441
  • Bhutan (འབྲུག)+975
  • Bolivia+591
  • Bosnia and Herzegovina (Босна и Херцеговина)+387
  • Botswana+267
  • Brazil (Brasil)+55
  • British Indian Ocean Territory+246
  • British Virgin Islands+1284
  • Brunei+673
  • Bulgaria (България)+359
  • Burkina Faso+226
  • Burundi (Uburundi)+257
  • Cambodia (កម្ពុជា)+855
  • Cameroon (Cameroun)+237
  • Canada+1
  • Cape Verde (Kabu Verdi)+238
  • Caribbean Netherlands+599
  • Cayman Islands+1345
  • Central African Republic (République centrafricaine)+236
  • Chad (Tchad)+235
  • Chile+56
  • China (中国)+86
  • Christmas Island+61
  • Cocos (Keeling) Islands+61
  • Colombia+57
  • Comoros (‫جزر القمر‬‎)+269
  • Congo (DRC) (Jamhuri ya Kidemokrasia ya Kongo)+243
  • Congo (Republic) (Congo-Brazzaville)+242
  • Cook Islands+682
  • Costa Rica+506
  • Côte d’Ivoire+225
  • Croatia (Hrvatska)+385
  • Cuba+53
  • Curaçao+599
  • Cyprus (Κύπρος)+357
  • Czech Republic (Česká republika)+420
  • Denmark (Danmark)+45
  • Djibouti+253
  • Dominica+1767
  • Dominican Republic (República Dominicana)+1
  • Ecuador+593
  • Egypt (‫مصر‬‎)+20
  • El Salvador+503
  • Equatorial Guinea (Guinea Ecuatorial)+240
  • Eritrea+291
  • Estonia (Eesti)+372
  • Ethiopia+251
  • Falkland Islands (Islas Malvinas)+500
  • Faroe Islands (Føroyar)+298
  • Fiji+679
  • Finland (Suomi)+358
  • France+33
  • French Guiana (Guyane française)+594
  • French Polynesia (Polynésie française)+689
  • Gabon+241
  • Gambia+220
  • Georgia (საქართველო)+995
  • Germany (Deutschland)+49
  • Ghana (Gaana)+233
  • Gibraltar+350
  • Greece (Ελλάδα)+30
  • Greenland (Kalaallit Nunaat)+299
  • Grenada+1473
  • Guadeloupe+590
  • Guam+1671
  • Guatemala+502
  • Guernsey+44
  • Guinea (Guinée)+224
  • Guinea-Bissau (Guiné Bissau)+245
  • Guyana+592
  • Haiti+509
  • Honduras+504
  • Hong Kong (香港)+852
  • Hungary (Magyarország)+36
  • Iceland (Ísland)+354
  • India (भारत)+91
  • Indonesia+62
  • Iran (‫ایران‬‎)+98
  • Iraq (‫العراق‬‎)+964
  • Ireland+353
  • Isle of Man+44
  • Israel (‫ישראל‬‎)+972
  • Italy (Italia)+39
  • Jamaica+1876
  • Japan (日本)+81
  • Jersey+44
  • Jordan (‫الأردن‬‎)+962
  • Kazakhstan (Казахстан)+7
  • Kenya+254
  • Kiribati+686
  • Kosovo+383
  • Kuwait (‫الكويت‬‎)+965
  • Kyrgyzstan (Кыргызстан)+996
  • Laos (ລາວ)+856
  • Latvia (Latvija)+371
  • Lebanon (‫لبنان‬‎)+961
  • Lesotho+266
  • Liberia+231
  • Libya (‫ليبيا‬‎)+218
  • Liechtenstein+423
  • Lithuania (Lietuva)+370
  • Luxembourg+352
  • Macau (澳門)+853
  • Macedonia (FYROM) (Македонија)+389
  • Madagascar (Madagasikara)+261
  • Malawi+265
  • Malaysia+60
  • Maldives+960
  • Mali+223
  • Malta+356
  • Marshall Islands+692
  • Martinique+596
  • Mauritania (‫موريتانيا‬‎)+222
  • Mauritius (Moris)+230
  • Mayotte+262
  • Mexico (México)+52
  • Micronesia+691
  • Moldova (Republica Moldova)+373
  • Monaco+377
  • Mongolia (Монгол)+976
  • Montenegro (Crna Gora)+382
  • Montserrat+1664
  • Morocco (‫المغرب‬‎)+212
  • Mozambique (Moçambique)+258
  • Myanmar (Burma) (မြန်မာ)+95
  • Namibia (Namibië)+264
  • Nauru+674
  • Nepal (नेपाल)+977
  • Netherlands (Nederland)+31
  • New Caledonia (Nouvelle-Calédonie)+687
  • New Zealand+64
  • Nicaragua+505
  • Niger (Nijar)+227
  • Nigeria+234
  • Niue+683
  • Norfolk Island+672
  • North Korea (조선 민주주의 인민 공화국)+850
  • Northern Mariana Islands+1670
  • Norway (Norge)+47
  • Oman (‫عُمان‬‎)+968
  • Pakistan (‫پاکستان‬‎)+92
  • Palau+680
  • Palestine (‫فلسطين‬‎)+970
  • Panama (Panamá)+507
  • Papua New Guinea+675
  • Paraguay+595
  • Peru (Perú)+51
  • Philippines+63
  • Poland (Polska)+48
  • Portugal+351
  • Puerto Rico+1
  • Qatar (‫قطر‬‎)+974
  • Réunion (La Réunion)+262
  • Romania (România)+40
  • Russia (Россия)+7
  • Rwanda+250
  • Saint Barthélemy (Saint-Barthélemy)+590
  • Saint Helena+290
  • Saint Kitts and Nevis+1869
  • Saint Lucia+1758
  • Saint Martin (Saint-Martin (partie française))+590
  • Saint Pierre and Miquelon (Saint-Pierre-et-Miquelon)+508
  • Saint Vincent and the Grenadines+1784
  • Samoa+685
  • San Marino+378
  • São Tomé and Príncipe (São Tomé e Príncipe)+239
  • Saudi Arabia (‫المملكة العربية السعودية‬‎)+966
  • Senegal (Sénégal)+221
  • Serbia (Србија)+381
  • Seychelles+248
  • Sierra Leone+232
  • Singapore+65
  • Sint Maarten+1721
  • Slovakia (Slovensko)+421
  • Slovenia (Slovenija)+386
  • Solomon Islands+677
  • Somalia (Soomaaliya)+252
  • South Africa+27
  • South Korea (대한민국)+82
  • South Sudan (‫جنوب السودان‬‎)+211
  • Spain (España)+34
  • Sri Lanka (ශ්‍රී ලංකාව)+94
  • Sudan (‫السودان‬‎)+249
  • Suriname+597
  • Svalbard and Jan Mayen+47
  • Swaziland+268
  • Sweden (Sverige)+46
  • Switzerland (Schweiz)+41
  • Syria (‫سوريا‬‎)+963
  • Taiwan (台灣)+886
  • Tajikistan+992
  • Tanzania+255
  • Thailand (ไทย)+66
  • Timor-Leste+670
  • Togo+228
  • Tokelau+690
  • Tonga+676
  • Trinidad and Tobago+1868
  • Tunisia (‫تونس‬‎)+216
  • Turkey (Türkiye)+90
  • Turkmenistan+993
  • Turks and Caicos Islands+1649
  • Tuvalu+688
  • U.S. Virgin Islands+1340
  • Uganda+256
  • Ukraine (Україна)+380
  • United Arab Emirates (‫الإمارات العربية المتحدة‬‎)+971
  • United Kingdom+44
  • United States+1
  • Uruguay+598
  • Uzbekistan (Oʻzbekiston)+998
  • Vanuatu+678
  • Vatican City (Città del Vaticano)+39
  • Venezuela+58
  • Vietnam (Việt Nam)+84
  • Wallis and Futuna+681
  • Western Sahara (‫الصحراء الغربية‬‎)+212
  • Yemen (‫اليمن‬‎)+967
  • Zambia+260
  • Zimbabwe+263
  • Åland Islands+358
Thanks! We'll be in touch in the next 12 hours
Oops! Something went wrong while submitting the form.

Mastering Prow: A Guide to Developing Your Own Plugin for Kubernetes CI/CD Workflow

Bhavya Jain

Cloud & DevOps

Continuous Integration and Continuous Delivery (CI/CD) pipelines are essential components of modern software development, especially in the world of Kubernetes and containerized applications. To facilitate these pipelines, many organizations use Prow, a CI/CD system built specifically for Kubernetes. While Prow offers a rich set of features out of the box, you may need to develop your own plugins to tailor the system to your organization’s requirements. In this guide, we’ll explore the world of Prow plugin development and show you how to get started.

Prerequisites

Before diving into Prow plugin development, ensure you have the following prerequisites:

  • Basic Knowledge of Kubernetes and CI/CD Concepts: Familiarity with Kubernetes concepts such as Pods, Deployments, and Services, as well as understanding CI/CD principles, will be beneficial for understanding Prow plugin development.

  • Access to a Kubernetes Cluster: You'll need access to a Kubernetes cluster for testing your plugins. If you don't have one already, you can set up a local cluster using tools like Minikube or use a cloud provider's managed Kubernetes service.

  • Prow Setup: Install and configure Prow in your Kubernetes cluster. You can visit Velotio Technologies - Getting Started with Prow: A Kubernetes-Native CI/CD Framework

  • Development Environment Setup: Ensure you have Git, Go, and Docker installed on your local machine for developing and testing Prow plugins. You'll also need to configure your environment to interact with your organization's Prow setup.

The Need for Custom Prow Plugins

While Prow provides a wide range of built-in plugins, your organization’s Kubernetes workflow may have specific requirements that aren’t covered by these defaults. This is where developing custom Prow plugins comes into play. Custom plugins allow you to extend Prow’s functionality to cater to your needs. Whether automating workflows, integrating with other tools, or enforcing custom policies, developing your own Prow plugins gives you the power to tailor your CI/CD pipeline precisely.

Getting Started with Prow Plugin Development

Developing a custom Prow plugin may seem daunting, but with the right approach and tools, it can be a rewarding experience. Here’s a step-by-step guide to get you started:

1. Set Up Your Development Environment

Before diving into plugin development, you need to set up your development environment. You will need Git, Go, and access to a Kubernetes cluster for testing your plugins. Ensure you have the necessary permissions to make changes to your organization’s Prow setup.

2. Choose a Plugin Type

Prow supports various plugin types, including postsubmits, presubmits, triggers, and utilities. Choose the type that best fits your use case.

  • Postsubmits: These plugins are executed after the code is merged and are often used for tasks like publishing artifacts or creating release notes.
  • Presubmits: Presubmit plugins run before code is merged, typically used for running tests and ensuring code quality.
  • Triggers: Trigger plugins allow you to trigger custom jobs based on specific events or criteria.
  • Utilities: Utility plugins offer reusable functions and utilities for other plugins.

3. Create Your Plugin

Once you’ve chosen a plugin type, it’s time to create it. Below is an example of a simple Prow plugin written in Go, named comment-plugin.go. It will create a comment on a pull request each time an event is received.

This code sets up a basic HTTP server that listens for GitHub events and handles them by creating a comment using the GitHub API. Customize this code to fit your specific use case.

package main
import (
"encoding/json"
"flag"
"net/http"
"os"
"strconv"
"time"
"github.com/sirupsen/logrus"
"k8s.io/test-infra/pkg/flagutil"
"k8s.io/test-infra/prow/config"
"k8s.io/test-infra/prow/config/secret"
prowflagutil "k8s.io/test-infra/prow/flagutil"
configflagutil "k8s.io/test-infra/prow/flagutil/config"
"k8s.io/test-infra/prow/github"
"k8s.io/test-infra/prow/interrupts"
"k8s.io/test-infra/prow/logrusutil"
"k8s.io/test-infra/prow/pjutil"
"k8s.io/test-infra/prow/pluginhelp"
"k8s.io/test-infra/prow/pluginhelp/externalplugins"
)
const pluginName = "comment-plugin"
type options struct {
port int
config configflagutil.ConfigOptions
dryRun bool
github prowflagutil.GitHubOptions
instrumentationOptions prowflagutil.InstrumentationOptions
webhookSecretFile string
}
type server struct {
tokenGenerator func() []byte
botUser *github.UserData
email string
ghc github.Client
log *logrus.Entry
repos []github.Repo
}
func helpProvider(_ []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
pluginHelp := &pluginhelp.PluginHelp{
Description: `The sample plugin`,
}
return pluginHelp, nil
}
func (o *options) Validate() error {
return nil
}
func gatherOptions() options {
o := options{config: configflagutil.ConfigOptions{ConfigPath: "./config.yaml"}}
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
fs.IntVar(&o.port, "port", 8888, "Port to listen on.")
fs.BoolVar(&o.dryRun, "dry-run", false, "Dry run for testing. Uses API tokens but does not mutate.")
fs.StringVar(&o.webhookSecretFile, "hmac-secret-file", "/etc/hmac", "Path to the file containing GitHub HMAC secret.")
for _, group := range []flagutil.OptionGroup{&o.github} {
group.AddFlags(fs)
}
fs.Parse(os.Args[1:])
return o
}
func main() {
o := gatherOptions()
if err := o.Validate(); err != nil {
logrus.Fatalf("Invalid options: %v", err)
}
logrusutil.ComponentInit()
log := logrus.StandardLogger().WithField("plugin", pluginName)
if err := secret.Add(o.webhookSecretFile); err != nil {
logrus.WithError(err).Fatal("Error starting secrets agent.")
}
gitHubClient, err := o.github.GitHubClient(o.dryRun)
if err != nil {
logrus.WithError(err).Fatal("Error getting GitHub client.")
}
email, err := gitHubClient.Email()
if err != nil {
log.WithError(err).Fatal("Error getting bot e-mail.")
}
botUser, err := gitHubClient.BotUser()
if err != nil {
logrus.WithError(err).Fatal("Error getting bot name.")
}
repos, err := gitHubClient.GetRepos(botUser.Login, true)
if err != nil {
log.WithError(err).Fatal("Error listing bot repositories.")
}
serv := &server{
tokenGenerator: secret.GetTokenGenerator(o.webhookSecretFile),
botUser: botUser,
email: email,
ghc: gitHubClient,
log: log,
repos: repos,
}
health := pjutil.NewHealthOnPort(o.instrumentationOptions.HealthPort)
health.ServeReady()
mux := http.NewServeMux()
mux.Handle("/", serv)
externalplugins.ServeExternalPluginHelp(mux, log, helpProvider)
logrus.Info("starting server " + strconv.Itoa(o.port))
httpServer := &http.Server{Addr: ":" + strconv.Itoa(o.port), Handler: mux}
defer interrupts.WaitForGracefulShutdown()
interrupts.ListenAndServe(httpServer, 5*time.Second)
}
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logrus.Info("inside http server")
_, _, payload, ok, _ := github.ValidateWebhook(w, r, s.tokenGenerator)
logrus.Info(string(payload))
if !ok {
return
}
logrus.Info(w, "Event received. Have a nice day.")
if err := s.handleEvent(payload); err != nil {
logrus.WithError(err).Error("Error parsing event.")
}
}
func (s *server) handleEvent(payload []byte) error {
logrus.Info("inside handler")
var pr github.PullRequestEvent
if err := json.Unmarshal(payload, &pr); err != nil {
return err
}
logrus.Info(pr.Number)
if err := s.ghc.CreateComment(pr.PullRequest.Base.Repo.Owner.Login, pr.PullRequest.Base.Repo.Name, pr.Number, "comment from smaple-plugin"); err != nil {
return err
}
return nil
}
view raw .golang hosted with ❤ by GitHub

4. Deploy Your Plugin

To deploy your custom Prow plugin, you will need to create a Docker image and deploy it into your Prow cluster.

FROM golang as app-builder
WORKDIR /app
RUN apt update
RUN apt-get install git
COPY . .
RUN CGO_ENABLED=0 go build -o main
FROM alpine:3.9
RUN apk add ca-certificates git
COPY --from=app-builder /app/main /app/custom-plugin
ENTRYPOINT ["/app/custom-plugin"]
view raw .txt hosted with ❤ by GitHub

docker build -t jainbhavya65/custom-plugin:v1 .

docker push jainbhavya65/custom-plugin:v1

Deploy the Docker image using Kubernetes deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
name: comment-plugin
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: comment-plugin
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: comment-plugin
spec:
containers:
- args:
- --github-token-path=/etc/github/oauth
- --hmac-secret-file=/etc/hmac-token/hmac
- --port=80
image: <IMAGE>
imagePullPolicy: Always
name: comment-plugin
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- mountPath: /etc/github
name: oauth
readOnly: true
- mountPath: /etc/hmac-token
name: hmac
readOnly: true
volumes:
- name: oauth
secret:
defaultMode: 420
secretName: oauth-token
- name: hmac
secret:
defaultMode: 420
secretName: hmac-token
view raw .yaml hosted with ❤ by GitHub

Create a service for deployment:

apiVersion: v1
kind: Service
metadata:
name: comment-plugin
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: comment-plugin
sessionAffinity: None
type: ClusterIP
view raw .yaml hosted with ❤ by GitHub

After creating the deployment and service, integrate it into your organization’s Prow configuration. This involves updating your Prow plugin.yaml files to include your plugin and specify when it should run.

external_plugins:
- name: comment-plugin
# No endpoint specified implies "http://{{name}}". // as we deploy plugin into same cluster
# if plugin is not deployed in same cluster then you can give endpoint
events:
# only pull request and issue comment events are send to our plugin
- pull_request
- issue_comment
view raw .yaml hosted with ❤ by GitHub

Conclusion

Mastering Prow plugin development opens up a world of possibilities for tailoring your Kubernetes CI/CD workflow to meet your organization’s needs. While the initial learning curve may be steep, the benefits of custom plugins in terms of automation, efficiency, and control are well worth the effort.

Remember that the key to successful Prow plugin development lies in clear documentation, thorough testing, and collaboration with your team to ensure that your custom plugins enhance your CI/CD pipeline’s functionality and reliability. As Kubernetes and containerized applications continue to evolve, Prow will remain a valuable tool for managing your CI/CD processes, and your custom plugins will be the secret sauce that sets your workflow apart from the rest.

Get the latest engineering blogs delivered straight to your inbox.
No spam. Only expert insights.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Did you like the blog? If yes, we're sure you'll also like to work with the people who write them - our best-in-class engineering team.

We're looking for talented developers who are passionate about new emerging technologies. If that's you, get in touch with us.

Explore current openings

Mastering Prow: A Guide to Developing Your Own Plugin for Kubernetes CI/CD Workflow

Continuous Integration and Continuous Delivery (CI/CD) pipelines are essential components of modern software development, especially in the world of Kubernetes and containerized applications. To facilitate these pipelines, many organizations use Prow, a CI/CD system built specifically for Kubernetes. While Prow offers a rich set of features out of the box, you may need to develop your own plugins to tailor the system to your organization’s requirements. In this guide, we’ll explore the world of Prow plugin development and show you how to get started.

Prerequisites

Before diving into Prow plugin development, ensure you have the following prerequisites:

  • Basic Knowledge of Kubernetes and CI/CD Concepts: Familiarity with Kubernetes concepts such as Pods, Deployments, and Services, as well as understanding CI/CD principles, will be beneficial for understanding Prow plugin development.

  • Access to a Kubernetes Cluster: You'll need access to a Kubernetes cluster for testing your plugins. If you don't have one already, you can set up a local cluster using tools like Minikube or use a cloud provider's managed Kubernetes service.

  • Prow Setup: Install and configure Prow in your Kubernetes cluster. You can visit Velotio Technologies - Getting Started with Prow: A Kubernetes-Native CI/CD Framework

  • Development Environment Setup: Ensure you have Git, Go, and Docker installed on your local machine for developing and testing Prow plugins. You'll also need to configure your environment to interact with your organization's Prow setup.

The Need for Custom Prow Plugins

While Prow provides a wide range of built-in plugins, your organization’s Kubernetes workflow may have specific requirements that aren’t covered by these defaults. This is where developing custom Prow plugins comes into play. Custom plugins allow you to extend Prow’s functionality to cater to your needs. Whether automating workflows, integrating with other tools, or enforcing custom policies, developing your own Prow plugins gives you the power to tailor your CI/CD pipeline precisely.

Getting Started with Prow Plugin Development

Developing a custom Prow plugin may seem daunting, but with the right approach and tools, it can be a rewarding experience. Here’s a step-by-step guide to get you started:

1. Set Up Your Development Environment

Before diving into plugin development, you need to set up your development environment. You will need Git, Go, and access to a Kubernetes cluster for testing your plugins. Ensure you have the necessary permissions to make changes to your organization’s Prow setup.

2. Choose a Plugin Type

Prow supports various plugin types, including postsubmits, presubmits, triggers, and utilities. Choose the type that best fits your use case.

  • Postsubmits: These plugins are executed after the code is merged and are often used for tasks like publishing artifacts or creating release notes.
  • Presubmits: Presubmit plugins run before code is merged, typically used for running tests and ensuring code quality.
  • Triggers: Trigger plugins allow you to trigger custom jobs based on specific events or criteria.
  • Utilities: Utility plugins offer reusable functions and utilities for other plugins.

3. Create Your Plugin

Once you’ve chosen a plugin type, it’s time to create it. Below is an example of a simple Prow plugin written in Go, named comment-plugin.go. It will create a comment on a pull request each time an event is received.

This code sets up a basic HTTP server that listens for GitHub events and handles them by creating a comment using the GitHub API. Customize this code to fit your specific use case.

package main
import (
"encoding/json"
"flag"
"net/http"
"os"
"strconv"
"time"
"github.com/sirupsen/logrus"
"k8s.io/test-infra/pkg/flagutil"
"k8s.io/test-infra/prow/config"
"k8s.io/test-infra/prow/config/secret"
prowflagutil "k8s.io/test-infra/prow/flagutil"
configflagutil "k8s.io/test-infra/prow/flagutil/config"
"k8s.io/test-infra/prow/github"
"k8s.io/test-infra/prow/interrupts"
"k8s.io/test-infra/prow/logrusutil"
"k8s.io/test-infra/prow/pjutil"
"k8s.io/test-infra/prow/pluginhelp"
"k8s.io/test-infra/prow/pluginhelp/externalplugins"
)
const pluginName = "comment-plugin"
type options struct {
port int
config configflagutil.ConfigOptions
dryRun bool
github prowflagutil.GitHubOptions
instrumentationOptions prowflagutil.InstrumentationOptions
webhookSecretFile string
}
type server struct {
tokenGenerator func() []byte
botUser *github.UserData
email string
ghc github.Client
log *logrus.Entry
repos []github.Repo
}
func helpProvider(_ []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
pluginHelp := &pluginhelp.PluginHelp{
Description: `The sample plugin`,
}
return pluginHelp, nil
}
func (o *options) Validate() error {
return nil
}
func gatherOptions() options {
o := options{config: configflagutil.ConfigOptions{ConfigPath: "./config.yaml"}}
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
fs.IntVar(&o.port, "port", 8888, "Port to listen on.")
fs.BoolVar(&o.dryRun, "dry-run", false, "Dry run for testing. Uses API tokens but does not mutate.")
fs.StringVar(&o.webhookSecretFile, "hmac-secret-file", "/etc/hmac", "Path to the file containing GitHub HMAC secret.")
for _, group := range []flagutil.OptionGroup{&o.github} {
group.AddFlags(fs)
}
fs.Parse(os.Args[1:])
return o
}
func main() {
o := gatherOptions()
if err := o.Validate(); err != nil {
logrus.Fatalf("Invalid options: %v", err)
}
logrusutil.ComponentInit()
log := logrus.StandardLogger().WithField("plugin", pluginName)
if err := secret.Add(o.webhookSecretFile); err != nil {
logrus.WithError(err).Fatal("Error starting secrets agent.")
}
gitHubClient, err := o.github.GitHubClient(o.dryRun)
if err != nil {
logrus.WithError(err).Fatal("Error getting GitHub client.")
}
email, err := gitHubClient.Email()
if err != nil {
log.WithError(err).Fatal("Error getting bot e-mail.")
}
botUser, err := gitHubClient.BotUser()
if err != nil {
logrus.WithError(err).Fatal("Error getting bot name.")
}
repos, err := gitHubClient.GetRepos(botUser.Login, true)
if err != nil {
log.WithError(err).Fatal("Error listing bot repositories.")
}
serv := &server{
tokenGenerator: secret.GetTokenGenerator(o.webhookSecretFile),
botUser: botUser,
email: email,
ghc: gitHubClient,
log: log,
repos: repos,
}
health := pjutil.NewHealthOnPort(o.instrumentationOptions.HealthPort)
health.ServeReady()
mux := http.NewServeMux()
mux.Handle("/", serv)
externalplugins.ServeExternalPluginHelp(mux, log, helpProvider)
logrus.Info("starting server " + strconv.Itoa(o.port))
httpServer := &http.Server{Addr: ":" + strconv.Itoa(o.port), Handler: mux}
defer interrupts.WaitForGracefulShutdown()
interrupts.ListenAndServe(httpServer, 5*time.Second)
}
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logrus.Info("inside http server")
_, _, payload, ok, _ := github.ValidateWebhook(w, r, s.tokenGenerator)
logrus.Info(string(payload))
if !ok {
return
}
logrus.Info(w, "Event received. Have a nice day.")
if err := s.handleEvent(payload); err != nil {
logrus.WithError(err).Error("Error parsing event.")
}
}
func (s *server) handleEvent(payload []byte) error {
logrus.Info("inside handler")
var pr github.PullRequestEvent
if err := json.Unmarshal(payload, &pr); err != nil {
return err
}
logrus.Info(pr.Number)
if err := s.ghc.CreateComment(pr.PullRequest.Base.Repo.Owner.Login, pr.PullRequest.Base.Repo.Name, pr.Number, "comment from smaple-plugin"); err != nil {
return err
}
return nil
}
view raw .golang hosted with ❤ by GitHub

4. Deploy Your Plugin

To deploy your custom Prow plugin, you will need to create a Docker image and deploy it into your Prow cluster.

FROM golang as app-builder
WORKDIR /app
RUN apt update
RUN apt-get install git
COPY . .
RUN CGO_ENABLED=0 go build -o main
FROM alpine:3.9
RUN apk add ca-certificates git
COPY --from=app-builder /app/main /app/custom-plugin
ENTRYPOINT ["/app/custom-plugin"]
view raw .txt hosted with ❤ by GitHub

docker build -t jainbhavya65/custom-plugin:v1 .

docker push jainbhavya65/custom-plugin:v1

Deploy the Docker image using Kubernetes deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
name: comment-plugin
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: comment-plugin
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: comment-plugin
spec:
containers:
- args:
- --github-token-path=/etc/github/oauth
- --hmac-secret-file=/etc/hmac-token/hmac
- --port=80
image: <IMAGE>
imagePullPolicy: Always
name: comment-plugin
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- mountPath: /etc/github
name: oauth
readOnly: true
- mountPath: /etc/hmac-token
name: hmac
readOnly: true
volumes:
- name: oauth
secret:
defaultMode: 420
secretName: oauth-token
- name: hmac
secret:
defaultMode: 420
secretName: hmac-token
view raw .yaml hosted with ❤ by GitHub

Create a service for deployment:

apiVersion: v1
kind: Service
metadata:
name: comment-plugin
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: comment-plugin
sessionAffinity: None
type: ClusterIP
view raw .yaml hosted with ❤ by GitHub

After creating the deployment and service, integrate it into your organization’s Prow configuration. This involves updating your Prow plugin.yaml files to include your plugin and specify when it should run.

external_plugins:
- name: comment-plugin
# No endpoint specified implies "http://{{name}}". // as we deploy plugin into same cluster
# if plugin is not deployed in same cluster then you can give endpoint
events:
# only pull request and issue comment events are send to our plugin
- pull_request
- issue_comment
view raw .yaml hosted with ❤ by GitHub

Conclusion

Mastering Prow plugin development opens up a world of possibilities for tailoring your Kubernetes CI/CD workflow to meet your organization’s needs. While the initial learning curve may be steep, the benefits of custom plugins in terms of automation, efficiency, and control are well worth the effort.

Remember that the key to successful Prow plugin development lies in clear documentation, thorough testing, and collaboration with your team to ensure that your custom plugins enhance your CI/CD pipeline’s functionality and reliability. As Kubernetes and containerized applications continue to evolve, Prow will remain a valuable tool for managing your CI/CD processes, and your custom plugins will be the secret sauce that sets your workflow apart from the rest.

Did you like the blog? If yes, we're sure you'll also like to work with the people who write them - our best-in-class engineering team.

We're looking for talented developers who are passionate about new emerging technologies. If that's you, get in touch with us.

Explore current openings