Add SigningKey to CommitSpec
Signed-off-by: LWJ <lwjames1996@gmail.com>
This commit is contained in:
		
							parent
							
								
									daad724ad2
								
							
						
					
					
						commit
						4aa56f1013
					
				|  | @ -106,6 +106,9 @@ type CommitSpec struct { | ||||||
| 	// AuthorEmail gives the email to provide when making a commit
 | 	// AuthorEmail gives the email to provide when making a commit
 | ||||||
| 	// +required
 | 	// +required
 | ||||||
| 	AuthorEmail string `json:"authorEmail"` | 	AuthorEmail string `json:"authorEmail"` | ||||||
|  | 	// SigningKey provides the option to sign commits with a GPG key
 | ||||||
|  | 	// +optional
 | ||||||
|  | 	SigningKey *SigningKey `json:"signingKey,omitempty"` | ||||||
| 	// MessageTemplate provides a template for the commit message,
 | 	// MessageTemplate provides a template for the commit message,
 | ||||||
| 	// into which will be interpolated the details of the change made.
 | 	// into which will be interpolated the details of the change made.
 | ||||||
| 	// +optional
 | 	// +optional
 | ||||||
|  | @ -142,6 +145,16 @@ type ImageUpdateAutomationStatus struct { | ||||||
| 	meta.ReconcileRequestStatus `json:",inline"` | 	meta.ReconcileRequestStatus `json:",inline"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // SigningKey references a Kubernetes secret that contains a GPG keypair
 | ||||||
|  | type SigningKey struct { | ||||||
|  | 	// SecretRef holds the name to a secret that contains a 'value' key
 | ||||||
|  | 	// with the ASCII Armored file (.asc) containing the GPG signing
 | ||||||
|  | 	// keypair as the value. It must be in the same namespace as the
 | ||||||
|  | 	// ImageUpdateAutomation.
 | ||||||
|  | 	// +required
 | ||||||
|  | 	SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const ( | const ( | ||||||
| 	// GitNotAvailableReason is used for ConditionReady when the
 | 	// GitNotAvailableReason is used for ConditionReady when the
 | ||||||
| 	// automation run cannot proceed because the git repository is
 | 	// automation run cannot proceed because the git repository is
 | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ limitations under the License. | ||||||
| package v1alpha1 | package v1alpha1 | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"github.com/fluxcd/pkg/apis/meta" | ||||||
| 	"k8s.io/apimachinery/pkg/apis/meta/v1" | 	"k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||||
| ) | ) | ||||||
|  | @ -28,6 +29,11 @@ import ( | ||||||
| // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
| func (in *CommitSpec) DeepCopyInto(out *CommitSpec) { | func (in *CommitSpec) DeepCopyInto(out *CommitSpec) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
|  | 	if in.SigningKey != nil { | ||||||
|  | 		in, out := &in.SigningKey, &out.SigningKey | ||||||
|  | 		*out = new(SigningKey) | ||||||
|  | 		(*in).DeepCopyInto(*out) | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommitSpec.
 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommitSpec.
 | ||||||
|  | @ -125,7 +131,7 @@ func (in *ImageUpdateAutomationSpec) DeepCopyInto(out *ImageUpdateAutomationSpec | ||||||
| 		*out = new(UpdateStrategy) | 		*out = new(UpdateStrategy) | ||||||
| 		**out = **in | 		**out = **in | ||||||
| 	} | 	} | ||||||
| 	out.Commit = in.Commit | 	in.Commit.DeepCopyInto(&out.Commit) | ||||||
| 	if in.Push != nil { | 	if in.Push != nil { | ||||||
| 		in, out := &in.Push, &out.Push | 		in, out := &in.Push, &out.Push | ||||||
| 		*out = new(PushSpec) | 		*out = new(PushSpec) | ||||||
|  | @ -189,6 +195,26 @@ func (in *PushSpec) DeepCopy() *PushSpec { | ||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
|  | func (in *SigningKey) DeepCopyInto(out *SigningKey) { | ||||||
|  | 	*out = *in | ||||||
|  | 	if in.SecretRef != nil { | ||||||
|  | 		in, out := &in.SecretRef, &out.SecretRef | ||||||
|  | 		*out = new(meta.LocalObjectReference) | ||||||
|  | 		**out = **in | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SigningKey.
 | ||||||
|  | func (in *SigningKey) DeepCopy() *SigningKey { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(SigningKey) | ||||||
|  | 	in.DeepCopyInto(out) | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
| func (in *UpdateStrategy) DeepCopyInto(out *UpdateStrategy) { | func (in *UpdateStrategy) DeepCopyInto(out *UpdateStrategy) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
|  |  | ||||||
|  | @ -80,6 +80,23 @@ spec: | ||||||
|                       message, into which will be interpolated the details of the |                       message, into which will be interpolated the details of the | ||||||
|                       change made. |                       change made. | ||||||
|                     type: string |                     type: string | ||||||
|  |                   signingKey: | ||||||
|  |                     description: SigningKey provides the option to sign commits with | ||||||
|  |                       a GPG key | ||||||
|  |                     properties: | ||||||
|  |                       secretRef: | ||||||
|  |                         description: SecretRef holds the name to a secret that contains | ||||||
|  |                           a 'value' key with the ASCII Armored file (.asc) containing | ||||||
|  |                           the GPG signing keypair as the value. It must be in the | ||||||
|  |                           same namespace as the ImageUpdateAutomation. | ||||||
|  |                         properties: | ||||||
|  |                           name: | ||||||
|  |                             description: Name of the referent | ||||||
|  |                             type: string | ||||||
|  |                         required: | ||||||
|  |                         - name | ||||||
|  |                         type: object | ||||||
|  |                     type: object | ||||||
|                 required: |                 required: | ||||||
|                 - authorEmail |                 - authorEmail | ||||||
|                 - authorName |                 - authorName | ||||||
|  |  | ||||||
|  | @ -17,9 +17,11 @@ limitations under the License. | ||||||
| package controllers | package controllers | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"golang.org/x/crypto/openpgp" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"math" | 	"math" | ||||||
| 	"os" | 	"os" | ||||||
|  | @ -227,10 +229,15 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr | ||||||
| 
 | 
 | ||||||
| 	var statusMessage string | 	var statusMessage string | ||||||
| 
 | 
 | ||||||
|  | 	var signingEntity *openpgp.Entity | ||||||
|  | 	if auto.Spec.Commit.SigningKey != nil { | ||||||
|  | 		signingEntity, err = r.getSigningEntity(ctx, auto) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// The status message depends on what happens next. Since there's
 | 	// The status message depends on what happens next. Since there's
 | ||||||
| 	// more than one way to succeed, there's some if..else below, and
 | 	// more than one way to succeed, there's some if..else below, and
 | ||||||
| 	// early returns only on failure.
 | 	// early returns only on failure.
 | ||||||
| 	if rev, err := commitAll(ctx, repo, &auto.Spec.Commit, templateValues); err != nil { | 	if rev, err := commitAll(repo, &auto.Spec.Commit, templateValues, signingEntity); err != nil { | ||||||
| 		if err == errNoChanges { | 		if err == errNoChanges { | ||||||
| 			r.event(ctx, auto, events.EventSeverityInfo, "no updates made") | 			r.event(ctx, auto, events.EventSeverityInfo, "no updates made") | ||||||
| 			log.V(debug).Info("no changes made in working directory; no commit") | 			log.V(debug).Info("no changes made in working directory; no commit") | ||||||
|  | @ -439,7 +446,7 @@ func switchBranch(repo *gogit.Repository, pushBranch string) error { | ||||||
| 
 | 
 | ||||||
| var errNoChanges error = errors.New("no changes made to working directory") | var errNoChanges error = errors.New("no changes made to working directory") | ||||||
| 
 | 
 | ||||||
| func commitAll(ctx context.Context, repo *gogit.Repository, commit *imagev1.CommitSpec, values TemplateData) (string, error) { | func commitAll(repo *gogit.Repository, commit *imagev1.CommitSpec, values TemplateData, ent *openpgp.Entity) (string, error) { | ||||||
| 	working, err := repo.Worktree() | 	working, err := repo.Worktree() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
|  | @ -473,6 +480,7 @@ func commitAll(ctx context.Context, repo *gogit.Repository, commit *imagev1.Comm | ||||||
| 			Email: commit.AuthorEmail, | 			Email: commit.AuthorEmail, | ||||||
| 			When:  time.Now(), | 			When:  time.Now(), | ||||||
| 		}, | 		}, | ||||||
|  | 		SignKey: ent, | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
|  | @ -480,6 +488,36 @@ func commitAll(ctx context.Context, repo *gogit.Repository, commit *imagev1.Comm | ||||||
| 	return rev.String(), nil | 	return rev.String(), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // getSigningEntity retrieves an OpenPGP entity referenced by the
 | ||||||
|  | // provided imagev1.ImageUpdateAutomation for git commit signing
 | ||||||
|  | func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context, auto imagev1.ImageUpdateAutomation) (*openpgp.Entity, error) { | ||||||
|  | 	// get kubernetes secret
 | ||||||
|  | 	secretName := types.NamespacedName{ | ||||||
|  | 		Namespace: auto.GetNamespace(), | ||||||
|  | 		Name:      auto.Spec.Commit.SigningKey.SecretRef.Name, | ||||||
|  | 	} | ||||||
|  | 	var secret corev1.Secret | ||||||
|  | 	if err := r.Get(ctx, secretName, &secret); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("could not find signing key secret '%s': %w", secretName, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// get data from secret
 | ||||||
|  | 	data, ok := secret.Data["value"] | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, fmt.Errorf("signing key secret '%s' does not contain a 'value' key", secretName) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// read entity from secret value
 | ||||||
|  | 	entities, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(data)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("could not read signing key from secret '%s': %w", secretName, err) | ||||||
|  | 	} | ||||||
|  | 	if len(entities) > 1 { | ||||||
|  | 		return nil, fmt.Errorf("multiple entities read from secret '%s', could not determine which signing key to use", secretName) | ||||||
|  | 	} | ||||||
|  | 	return entities[0], nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // push pushes the branch given to the origin using the git library
 | // push pushes the branch given to the origin using the git library
 | ||||||
| // indicated by `impl`. It's passed both the path to the repo and a
 | // indicated by `impl`. It's passed both the path to the repo and a
 | ||||||
| // gogit.Repository value, since the latter may as well be used if the
 | // gogit.Repository value, since the latter may as well be used if the
 | ||||||
|  |  | ||||||
|  | @ -20,6 +20,8 @@ import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"golang.org/x/crypto/openpgp" | ||||||
|  | 	"golang.org/x/crypto/openpgp/armor" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"math/rand" | 	"math/rand" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | @ -388,6 +390,161 @@ Images: | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
|  | 	Context("commit signing", func() { | ||||||
|  | 
 | ||||||
|  | 		var localRepo *git.Repository | ||||||
|  | 
 | ||||||
|  | 		// generate keypair for signing
 | ||||||
|  | 		pgpEntity, err := openpgp.NewEntity("", "", "", nil) | ||||||
|  | 		Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 		BeforeEach(func() { | ||||||
|  | 			Expect(initGitRepo(gitServer, "testdata/appconfig", branch, repositoryPath)).To(Succeed()) | ||||||
|  | 			repoURL := gitServer.HTTPAddressWithCredentials() + repositoryPath | ||||||
|  | 			var err error | ||||||
|  | 			localRepo, err = git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{ | ||||||
|  | 				URL:           repoURL, | ||||||
|  | 				RemoteName:    "origin", | ||||||
|  | 				ReferenceName: plumbing.NewBranchReferenceName(branch), | ||||||
|  | 			}) | ||||||
|  | 			Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 			gitRepoKey := types.NamespacedName{ | ||||||
|  | 				Name:      "image-auto-" + randStringRunes(5), | ||||||
|  | 				Namespace: namespace.Name, | ||||||
|  | 			} | ||||||
|  | 			gitRepo := &sourcev1.GitRepository{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name:      gitRepoKey.Name, | ||||||
|  | 					Namespace: namespace.Name, | ||||||
|  | 				}, | ||||||
|  | 				Spec: sourcev1.GitRepositorySpec{ | ||||||
|  | 					URL:      repoURL, | ||||||
|  | 					Interval: metav1.Duration{Duration: time.Minute}, | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			Expect(k8sClient.Create(context.Background(), gitRepo)).To(Succeed()) | ||||||
|  | 			policyKey := types.NamespacedName{ | ||||||
|  | 				Name:      "policy-" + randStringRunes(5), | ||||||
|  | 				Namespace: namespace.Name, | ||||||
|  | 			} | ||||||
|  | 			// NB not testing the image reflector controller; this
 | ||||||
|  | 			// will make a "fully formed" ImagePolicy object.
 | ||||||
|  | 			policy := &imagev1_reflect.ImagePolicy{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name:      policyKey.Name, | ||||||
|  | 					Namespace: policyKey.Namespace, | ||||||
|  | 				}, | ||||||
|  | 				Spec: imagev1_reflect.ImagePolicySpec{ | ||||||
|  | 					ImageRepositoryRef: meta.LocalObjectReference{ | ||||||
|  | 						Name: "not-expected-to-exist", | ||||||
|  | 					}, | ||||||
|  | 					Policy: imagev1_reflect.ImagePolicyChoice{ | ||||||
|  | 						SemVer: &imagev1_reflect.SemVerPolicy{ | ||||||
|  | 							Range: "1.x", | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				Status: imagev1_reflect.ImagePolicyStatus{ | ||||||
|  | 					LatestImage: "helloworld:v1.0.0", | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			Expect(k8sClient.Create(context.Background(), policy)).To(Succeed()) | ||||||
|  | 			Expect(k8sClient.Status().Update(context.Background(), policy)).To(Succeed()) | ||||||
|  | 
 | ||||||
|  | 			// Insert a setter reference into the deployment file,
 | ||||||
|  | 			// before creating the automation object itself.
 | ||||||
|  | 			commitInRepo(repoURL, branch, "Install setter marker", func(tmp string) { | ||||||
|  | 				replaceMarker(tmp, policyKey) | ||||||
|  | 			}) | ||||||
|  | 
 | ||||||
|  | 			// pull the head commit we just pushed, so it's not
 | ||||||
|  | 			// considered a new commit when checking for a commit
 | ||||||
|  | 			// made by automation.
 | ||||||
|  | 			waitForNewHead(localRepo, branch) | ||||||
|  | 
 | ||||||
|  | 			// now create the automation object, and let it (one
 | ||||||
|  | 			// hopes!) make a commit itself.
 | ||||||
|  | 			updateKey := types.NamespacedName{ | ||||||
|  | 				Namespace: namespace.Name, | ||||||
|  | 				Name:      "update-test", | ||||||
|  | 			} | ||||||
|  | 			updateBySetters := &imagev1.ImageUpdateAutomation{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name:      updateKey.Name, | ||||||
|  | 					Namespace: updateKey.Namespace, | ||||||
|  | 				}, | ||||||
|  | 				Spec: imagev1.ImageUpdateAutomationSpec{ | ||||||
|  | 					Interval: metav1.Duration{Duration: 2 * time.Hour}, // this is to ensure any subsequent run should be outside the scope of the testing
 | ||||||
|  | 					Checkout: imagev1.GitCheckoutSpec{ | ||||||
|  | 						GitRepositoryRef: meta.LocalObjectReference{ | ||||||
|  | 							Name: gitRepoKey.Name, | ||||||
|  | 						}, | ||||||
|  | 						Branch: branch, | ||||||
|  | 					}, | ||||||
|  | 					Update: &imagev1.UpdateStrategy{ | ||||||
|  | 						Strategy: imagev1.UpdateStrategySetters, | ||||||
|  | 					}, | ||||||
|  | 					Commit: imagev1.CommitSpec{ | ||||||
|  | 						SigningKey: &imagev1.SigningKey{}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// configure OpenPGP armor encoder
 | ||||||
|  | 			b := bytes.NewBuffer(nil) | ||||||
|  | 			w, err := armor.Encode(b, openpgp.PrivateKeyType, nil) | ||||||
|  | 			Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 			// serialize private key
 | ||||||
|  | 			err = pgpEntity.SerializePrivate(w, nil) | ||||||
|  | 			Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 			err = w.Close() | ||||||
|  | 			Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 			// create the secret containing signing key
 | ||||||
|  | 			sec := &corev1.Secret{ | ||||||
|  | 				Data: map[string][]byte{ | ||||||
|  | 					"value": b.Bytes(), | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			sec.Name = "signing-key-secret-" + randStringRunes(5) | ||||||
|  | 			sec.Namespace = namespace.Name | ||||||
|  | 			Expect(k8sClient.Create(context.Background(), sec)).To(Succeed()) | ||||||
|  | 			updateBySetters.Spec.Commit.SigningKey.SecretRef = &meta.LocalObjectReference{Name: sec.Name} | ||||||
|  | 
 | ||||||
|  | 			Expect(k8sClient.Create(context.Background(), updateBySetters)).To(Succeed()) | ||||||
|  | 			// wait for a new commit to be made by the controller
 | ||||||
|  | 			waitForNewHead(localRepo, branch) | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		AfterEach(func() { | ||||||
|  | 			Expect(k8sClient.Delete(context.Background(), namespace)).To(Succeed()) | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		It("signs the commit with the generated GPG key", func() { | ||||||
|  | 			head, _ := localRepo.Head() | ||||||
|  | 			commit, err := localRepo.CommitObject(head.Hash()) | ||||||
|  | 			Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 			// configure OpenPGP armor encoder
 | ||||||
|  | 			b := bytes.NewBuffer(nil) | ||||||
|  | 			w, err := armor.Encode(b, openpgp.PublicKeyType, nil) | ||||||
|  | 			Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 			// serialize public key
 | ||||||
|  | 			err = pgpEntity.Serialize(w) | ||||||
|  | 			Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 			err = w.Close() | ||||||
|  | 			Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 			// verify commit
 | ||||||
|  | 			ent, err := commit.Verify(b.String()) | ||||||
|  | 			Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 			Expect(ent.PrimaryKey.Fingerprint).To(Equal(pgpEntity.PrimaryKey.Fingerprint)) | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
| 	endToEnd := func(impl, proto string) func() { | 	endToEnd := func(impl, proto string) func() { | ||||||
| 		return func() { | 		return func() { | ||||||
| 			var ( | 			var ( | ||||||
|  |  | ||||||
|  | @ -53,6 +53,20 @@ string | ||||||
| </tr> | </tr> | ||||||
| <tr> | <tr> | ||||||
| <td> | <td> | ||||||
|  | <code>signingKey</code><br> | ||||||
|  | <em> | ||||||
|  | <a href="#image.toolkit.fluxcd.io/v1alpha1.SigningKey"> | ||||||
|  | SigningKey | ||||||
|  | </a> | ||||||
|  | </em> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  | <em>(Optional)</em> | ||||||
|  | <p>SigningKey provides the option to sign commits with a GPG key</p> | ||||||
|  | </td> | ||||||
|  | </tr> | ||||||
|  | <tr> | ||||||
|  | <td> | ||||||
| <code>messageTemplate</code><br> | <code>messageTemplate</code><br> | ||||||
| <em> | <em> | ||||||
| string | string | ||||||
|  | @ -502,6 +516,40 @@ starting point, if it doesn’t already exist.</p> | ||||||
| </table> | </table> | ||||||
| </div> | </div> | ||||||
| </div> | </div> | ||||||
|  | <h3 id="image.toolkit.fluxcd.io/v1alpha1.SigningKey">SigningKey | ||||||
|  | </h3> | ||||||
|  | <p> | ||||||
|  | (<em>Appears on:</em> | ||||||
|  | <a href="#image.toolkit.fluxcd.io/v1alpha1.CommitSpec">CommitSpec</a>) | ||||||
|  | </p> | ||||||
|  | <p>SigningKey references a Kubernetes secret that contains a GPG file</p> | ||||||
|  | <div class="md-typeset__scrollwrap"> | ||||||
|  | <div class="md-typeset__table"> | ||||||
|  | <table> | ||||||
|  | <thead> | ||||||
|  | <tr> | ||||||
|  | <th>Field</th> | ||||||
|  | <th>Description</th> | ||||||
|  | </tr> | ||||||
|  | </thead> | ||||||
|  | <tbody> | ||||||
|  | <tr> | ||||||
|  | <td> | ||||||
|  | <code>secretRef</code><br> | ||||||
|  | <em> | ||||||
|  | <a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference"> | ||||||
|  | github.com/fluxcd/pkg/apis/meta.LocalObjectReference | ||||||
|  | </a> | ||||||
|  | </em> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  | <p>SecretRef holds the name to a secret that contains a ‘value’ key with the GPG file as the value. It must be in the same namespace as the ImageUpdateAutomation.</p> | ||||||
|  | </td> | ||||||
|  | </tr> | ||||||
|  | </tbody> | ||||||
|  | </table> | ||||||
|  | </div> | ||||||
|  | </div> | ||||||
| <h3 id="image.toolkit.fluxcd.io/v1alpha1.UpdateStrategy">UpdateStrategy | <h3 id="image.toolkit.fluxcd.io/v1alpha1.UpdateStrategy">UpdateStrategy | ||||||
| </h3> | </h3> | ||||||
| <p> | <p> | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							|  | @ -26,6 +26,7 @@ require ( | ||||||
| 	github.com/onsi/gomega v1.10.2 | 	github.com/onsi/gomega v1.10.2 | ||||||
| 	github.com/otiai10/copy v1.2.0 | 	github.com/otiai10/copy v1.2.0 | ||||||
| 	github.com/spf13/pflag v1.0.5 | 	github.com/spf13/pflag v1.0.5 | ||||||
|  | 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad | ||||||
| 	k8s.io/api v0.20.2 | 	k8s.io/api v0.20.2 | ||||||
| 	k8s.io/apimachinery v0.20.2 | 	k8s.io/apimachinery v0.20.2 | ||||||
| 	k8s.io/client-go v0.20.2 | 	k8s.io/client-go v0.20.2 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue