Merge pull request #5835 from fluxcd/create-secret-receiver
Add `flux create secret receiver` command
This commit is contained in:
@@ -30,6 +30,7 @@ import (
|
|||||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ var createReceiverCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type receiverFlags struct {
|
type receiverFlags struct {
|
||||||
receiverType string
|
receiverType flags.ReceiverType
|
||||||
secretRef string
|
secretRef string
|
||||||
events []string
|
events []string
|
||||||
resources []string
|
resources []string
|
||||||
@@ -58,7 +59,7 @@ type receiverFlags struct {
|
|||||||
var receiverArgs receiverFlags
|
var receiverArgs receiverFlags
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
createReceiverCmd.Flags().StringVar(&receiverArgs.receiverType, "type", "", "")
|
createReceiverCmd.Flags().Var(&receiverArgs.receiverType, "type", receiverArgs.receiverType.Description())
|
||||||
createReceiverCmd.Flags().StringVar(&receiverArgs.secretRef, "secret-ref", "", "")
|
createReceiverCmd.Flags().StringVar(&receiverArgs.secretRef, "secret-ref", "", "")
|
||||||
createReceiverCmd.Flags().StringSliceVar(&receiverArgs.events, "event", []string{}, "also accepts comma-separated values")
|
createReceiverCmd.Flags().StringSliceVar(&receiverArgs.events, "event", []string{}, "also accepts comma-separated values")
|
||||||
createReceiverCmd.Flags().StringSliceVar(&receiverArgs.resources, "resource", []string{}, "also accepts comma-separated values")
|
createReceiverCmd.Flags().StringSliceVar(&receiverArgs.resources, "resource", []string{}, "also accepts comma-separated values")
|
||||||
@@ -109,7 +110,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
Labels: sourceLabels,
|
Labels: sourceLabels,
|
||||||
},
|
},
|
||||||
Spec: notificationv1.ReceiverSpec{
|
Spec: notificationv1.ReceiverSpec{
|
||||||
Type: receiverArgs.receiverType,
|
Type: receiverArgs.receiverType.String(),
|
||||||
Events: receiverArgs.events,
|
Events: receiverArgs.events,
|
||||||
Resources: resources,
|
Resources: resources,
|
||||||
SecretRef: meta.LocalObjectReference{
|
SecretRef: meta.LocalObjectReference{
|
||||||
|
|||||||
@@ -56,6 +56,22 @@ func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
existing.StringData = secret.StringData
|
existing.StringData = secret.StringData
|
||||||
|
if secret.Annotations != nil {
|
||||||
|
if existing.Annotations == nil {
|
||||||
|
existing.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
for k, v := range secret.Annotations {
|
||||||
|
existing.Annotations[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if secret.Labels != nil {
|
||||||
|
if existing.Labels == nil {
|
||||||
|
existing.Labels = make(map[string]string)
|
||||||
|
}
|
||||||
|
for k, v := range secret.Labels {
|
||||||
|
existing.Labels[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := kubeClient.Update(ctx, &existing); err != nil {
|
if err := kubeClient.Update(ctx, &existing); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
134
cmd/flux/create_secret_receiver.go
Normal file
134
cmd/flux/create_secret_receiver.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 The Flux authors
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/v2/internal/flags"
|
||||||
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/v2/pkg/manifestgen/sourcesecret"
|
||||||
|
)
|
||||||
|
|
||||||
|
var createSecretReceiverCmd = &cobra.Command{
|
||||||
|
Use: "receiver [name]",
|
||||||
|
Short: "Create or update a Kubernetes secret for a Receiver webhook",
|
||||||
|
Long: `The create secret receiver command generates a Kubernetes secret with
|
||||||
|
the token used for webhook payload validation and an annotation with the
|
||||||
|
computed webhook URL.`,
|
||||||
|
Example: ` # Create a receiver secret for a GitHub webhook
|
||||||
|
flux create secret receiver github-receiver \
|
||||||
|
--namespace=my-namespace \
|
||||||
|
--type=github \
|
||||||
|
--hostname=flux.example.com \
|
||||||
|
--export
|
||||||
|
|
||||||
|
# Create a receiver secret for GCR with email claim
|
||||||
|
flux create secret receiver gcr-receiver \
|
||||||
|
--namespace=my-namespace \
|
||||||
|
--type=gcr \
|
||||||
|
--hostname=flux.example.com \
|
||||||
|
--email-claim=sa@project.iam.gserviceaccount.com \
|
||||||
|
--export`,
|
||||||
|
RunE: createSecretReceiverCmdRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
type secretReceiverFlags struct {
|
||||||
|
receiverType flags.ReceiverType
|
||||||
|
token string
|
||||||
|
hostname string
|
||||||
|
emailClaim string
|
||||||
|
audienceClaim string
|
||||||
|
}
|
||||||
|
|
||||||
|
var secretReceiverArgs secretReceiverFlags
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
createSecretReceiverCmd.Flags().Var(&secretReceiverArgs.receiverType, "type", secretReceiverArgs.receiverType.Description())
|
||||||
|
createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.token, "token", "", "webhook token used for payload validation and URL computation, auto-generated if not specified")
|
||||||
|
createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.hostname, "hostname", "", "hostname for the webhook URL e.g. flux.example.com")
|
||||||
|
createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.emailClaim, "email-claim", "", "IAM service account email, required for gcr type")
|
||||||
|
createSecretReceiverCmd.Flags().StringVar(&secretReceiverArgs.audienceClaim, "audience-claim", "", "custom OIDC token audience for gcr type, defaults to the webhook URL")
|
||||||
|
|
||||||
|
createSecretCmd.AddCommand(createSecretReceiverCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSecretReceiverCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
name := args[0]
|
||||||
|
|
||||||
|
if secretReceiverArgs.receiverType == "" {
|
||||||
|
return fmt.Errorf("--type is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretReceiverArgs.hostname == "" {
|
||||||
|
return fmt.Errorf("--hostname is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretReceiverArgs.receiverType.String() == notificationv1.GCRReceiver && secretReceiverArgs.emailClaim == "" {
|
||||||
|
return fmt.Errorf("--email-claim is required for gcr receiver type")
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, err := parseLabels()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := sourcesecret.Options{
|
||||||
|
Name: name,
|
||||||
|
Namespace: *kubeconfigArgs.Namespace,
|
||||||
|
Labels: labels,
|
||||||
|
ReceiverType: secretReceiverArgs.receiverType.String(),
|
||||||
|
Token: secretReceiverArgs.token,
|
||||||
|
Hostname: secretReceiverArgs.hostname,
|
||||||
|
EmailClaim: secretReceiverArgs.emailClaim,
|
||||||
|
AudienceClaim: secretReceiverArgs.audienceClaim,
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := sourcesecret.GenerateReceiver(opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if createArgs.export {
|
||||||
|
rootCmd.Println(secret.Content)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
|
defer cancel()
|
||||||
|
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var s corev1.Secret
|
||||||
|
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Actionf("receiver secret '%s' created in '%s' namespace", name, *kubeconfigArgs.Namespace)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
74
cmd/flux/create_secret_receiver_test.go
Normal file
74
cmd/flux/create_secret_receiver_test.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 The Flux authors
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateReceiverSecret(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
assert assertFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing type",
|
||||||
|
args: "create secret receiver test-secret --token=t --hostname=h",
|
||||||
|
assert: assertError("--type is required"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid type",
|
||||||
|
args: "create secret receiver test-secret --type=invalid --token=t --hostname=h",
|
||||||
|
assert: assertError("invalid argument \"invalid\" for \"--type\" flag: receiver type 'invalid' is not supported, must be one of: generic, generic-hmac, github, gitlab, bitbucket, harbor, dockerhub, quay, gcr, nexus, acr, cdevents"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing hostname",
|
||||||
|
args: "create secret receiver test-secret --type=github --token=t",
|
||||||
|
assert: assertError("--hostname is required"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gcr missing email-claim",
|
||||||
|
args: "create secret receiver test-secret --type=gcr --token=t --hostname=h",
|
||||||
|
assert: assertError("--email-claim is required for gcr receiver type"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "github receiver secret",
|
||||||
|
args: "create secret receiver receiver-secret --type=github --token=test-token --hostname=flux.example.com --namespace=my-namespace --export",
|
||||||
|
assert: assertGoldenFile("testdata/create_secret/receiver/secret-receiver.yaml"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gcr receiver secret",
|
||||||
|
args: "create secret receiver gcr-secret --type=gcr --token=test-token --hostname=flux.example.com --email-claim=sa@project.iam.gserviceaccount.com --namespace=my-namespace --export",
|
||||||
|
assert: assertGoldenFile("testdata/create_secret/receiver/secret-receiver-gcr.yaml"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gcr receiver secret with custom audience",
|
||||||
|
args: "create secret receiver gcr-secret --type=gcr --token=test-token --hostname=flux.example.com --email-claim=sa@project.iam.gserviceaccount.com --audience-claim=https://custom.audience.example.com --namespace=my-namespace --export",
|
||||||
|
assert: assertGoldenFile("testdata/create_secret/receiver/secret-receiver-gcr-audience.yaml"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cmd := cmdTestCase{
|
||||||
|
args: tt.args,
|
||||||
|
assert: tt.assert,
|
||||||
|
}
|
||||||
|
cmd.runTestCmd(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -456,6 +456,7 @@ func resetCmdArgs() {
|
|||||||
secretGitArgs = NewSecretGitFlags()
|
secretGitArgs = NewSecretGitFlags()
|
||||||
secretGitHubAppArgs = secretGitHubAppFlags{}
|
secretGitHubAppArgs = secretGitHubAppFlags{}
|
||||||
secretProxyArgs = secretProxyFlags{}
|
secretProxyArgs = secretProxyFlags{}
|
||||||
|
secretReceiverArgs = secretReceiverFlags{}
|
||||||
secretHelmArgs = secretHelmFlags{}
|
secretHelmArgs = secretHelmFlags{}
|
||||||
secretTLSArgs = secretTLSFlags{}
|
secretTLSArgs = secretTLSFlags{}
|
||||||
sourceBucketArgs = sourceBucketFlags{}
|
sourceBucketArgs = sourceBucketFlags{}
|
||||||
|
|||||||
13
cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr-audience.yaml
vendored
Normal file
13
cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr-audience.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
notification.toolkit.fluxcd.io/webhook: https://flux.example.com/hook/6d6c55e9affb9d1e0d101ce604ae4270880ec1ff24d1bd2d928fcd64243d21a4
|
||||||
|
name: gcr-secret
|
||||||
|
namespace: my-namespace
|
||||||
|
stringData:
|
||||||
|
audience: https://custom.audience.example.com
|
||||||
|
email: sa@project.iam.gserviceaccount.com
|
||||||
|
token: test-token
|
||||||
|
|
||||||
13
cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr.yaml
vendored
Normal file
13
cmd/flux/testdata/create_secret/receiver/secret-receiver-gcr.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
notification.toolkit.fluxcd.io/webhook: https://flux.example.com/hook/6d6c55e9affb9d1e0d101ce604ae4270880ec1ff24d1bd2d928fcd64243d21a4
|
||||||
|
name: gcr-secret
|
||||||
|
namespace: my-namespace
|
||||||
|
stringData:
|
||||||
|
audience: https://flux.example.com/hook/6d6c55e9affb9d1e0d101ce604ae4270880ec1ff24d1bd2d928fcd64243d21a4
|
||||||
|
email: sa@project.iam.gserviceaccount.com
|
||||||
|
token: test-token
|
||||||
|
|
||||||
11
cmd/flux/testdata/create_secret/receiver/secret-receiver.yaml
vendored
Normal file
11
cmd/flux/testdata/create_secret/receiver/secret-receiver.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
notification.toolkit.fluxcd.io/webhook: https://flux.example.com/hook/106120121d366c2f67e93200f6c1dbe938235eb588daa5e8c0516d3a77ac1dee
|
||||||
|
name: receiver-secret
|
||||||
|
namespace: my-namespace
|
||||||
|
stringData:
|
||||||
|
token: test-token
|
||||||
|
|
||||||
68
internal/flags/receiver_type.go
Normal file
68
internal/flags/receiver_type.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 The Flux authors
|
||||||
|
|
||||||
|
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 flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||||
|
|
||||||
|
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var supportedReceiverTypes = []string{
|
||||||
|
notificationv1.GenericReceiver,
|
||||||
|
notificationv1.GenericHMACReceiver,
|
||||||
|
notificationv1.GitHubReceiver,
|
||||||
|
notificationv1.GitLabReceiver,
|
||||||
|
notificationv1.BitbucketReceiver,
|
||||||
|
notificationv1.HarborReceiver,
|
||||||
|
notificationv1.DockerHubReceiver,
|
||||||
|
notificationv1.QuayReceiver,
|
||||||
|
notificationv1.GCRReceiver,
|
||||||
|
notificationv1.NexusReceiver,
|
||||||
|
notificationv1.ACRReceiver,
|
||||||
|
notificationv1.CDEventsReceiver,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReceiverType string
|
||||||
|
|
||||||
|
func (r *ReceiverType) String() string {
|
||||||
|
return string(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReceiverType) Set(str string) error {
|
||||||
|
if strings.TrimSpace(str) == "" {
|
||||||
|
return fmt.Errorf("no receiver type given, please specify %s",
|
||||||
|
r.Description())
|
||||||
|
}
|
||||||
|
if !utils.ContainsItemString(supportedReceiverTypes, str) {
|
||||||
|
return fmt.Errorf("receiver type '%s' is not supported, must be one of: %s",
|
||||||
|
str, strings.Join(supportedReceiverTypes, ", "))
|
||||||
|
}
|
||||||
|
*r = ReceiverType(str)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReceiverType) Type() string {
|
||||||
|
return strings.Join(supportedReceiverTypes, "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReceiverType) Description() string {
|
||||||
|
return "the receiver type"
|
||||||
|
}
|
||||||
@@ -42,6 +42,12 @@ const (
|
|||||||
KnownHostsSecretKey = "known_hosts"
|
KnownHostsSecretKey = "known_hosts"
|
||||||
BearerTokenKey = "bearerToken"
|
BearerTokenKey = "bearerToken"
|
||||||
TrustPolicyKey = "trustpolicy.json"
|
TrustPolicyKey = "trustpolicy.json"
|
||||||
|
TokenSecretKey = "token"
|
||||||
|
EmailSecretKey = "email"
|
||||||
|
AudienceSecretKey = "audience"
|
||||||
|
|
||||||
|
// WebhookURLAnnotation is the annotation key for the computed webhook URL.
|
||||||
|
WebhookURLAnnotation = "notification.toolkit.fluxcd.io/webhook"
|
||||||
|
|
||||||
// Deprecated: Replaced by CACrtSecretKey, but kept for backwards
|
// Deprecated: Replaced by CACrtSecretKey, but kept for backwards
|
||||||
// compatibility with deprecated TLS flags.
|
// compatibility with deprecated TLS flags.
|
||||||
@@ -82,6 +88,13 @@ type Options struct {
|
|||||||
GitHubAppInstallationID string
|
GitHubAppInstallationID string
|
||||||
GitHubAppPrivateKey string
|
GitHubAppPrivateKey string
|
||||||
GitHubAppBaseURL string
|
GitHubAppBaseURL string
|
||||||
|
|
||||||
|
// Receiver options
|
||||||
|
ReceiverType string
|
||||||
|
Token string
|
||||||
|
Hostname string
|
||||||
|
EmailClaim string
|
||||||
|
AudienceClaim string
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerificationCrt struct {
|
type VerificationCrt struct {
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ package sourcesecret
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -260,6 +263,59 @@ func GenerateGitHubApp(options Options) (*manifestgen.Manifest, error) {
|
|||||||
return secretToManifest(secret, options)
|
return secretToManifest(secret, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateReceiver(options Options) (*manifestgen.Manifest, error) {
|
||||||
|
token := options.Token
|
||||||
|
if token == "" {
|
||||||
|
b := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate random token: %w", err)
|
||||||
|
}
|
||||||
|
token = hex.EncodeToString(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Hostname == "" {
|
||||||
|
return nil, fmt.Errorf("hostname is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the webhook path using the same algorithm as notification-controller.
|
||||||
|
// See: github.com/fluxcd/notification-controller/api/v1.Receiver.GetWebhookPath
|
||||||
|
digest := sha256.Sum256([]byte(token + options.Name + options.Namespace))
|
||||||
|
webhookPath := fmt.Sprintf("/hook/%x", digest)
|
||||||
|
webhookURL := fmt.Sprintf("https://%s%s", options.Hostname, webhookPath)
|
||||||
|
|
||||||
|
secret := &corev1.Secret{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: options.Name,
|
||||||
|
Namespace: options.Namespace,
|
||||||
|
Labels: options.Labels,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
WebhookURLAnnotation: webhookURL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
StringData: map[string]string{
|
||||||
|
TokenSecretKey: token,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.ReceiverType == "gcr" {
|
||||||
|
if options.EmailClaim == "" {
|
||||||
|
return nil, fmt.Errorf("email-claim is required for gcr receiver type")
|
||||||
|
}
|
||||||
|
secret.StringData[EmailSecretKey] = options.EmailClaim
|
||||||
|
if options.AudienceClaim != "" {
|
||||||
|
secret.StringData[AudienceSecretKey] = options.AudienceClaim
|
||||||
|
} else {
|
||||||
|
secret.StringData[AudienceSecretKey] = webhookURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return secretToManifest(secret, options)
|
||||||
|
}
|
||||||
|
|
||||||
func LoadKeyPairFromPath(path, password string) (*ssh.KeyPair, error) {
|
func LoadKeyPairFromPath(path, password string) (*ssh.KeyPair, error) {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user