diff --git a/changeset/commit.go b/changeset/commit.go new file mode 100644 index 000000000..77ed70932 --- /dev/null +++ b/changeset/commit.go @@ -0,0 +1,64 @@ +/* +Copyright 2018 The Knative 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 changeset + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" +) + +const ( + commitIDFile = "HEAD" + koDataPathEnvName = "KO_DATA_PATH" +) + +var ( + commitIDRE = regexp.MustCompile(`^[a-f0-9]{40}$`) +) + +// Get tries to fetch the first 7 digitals of GitHub commit ID from HEAD file in +// KO_DATA_PATH. If it fails, it returns the error it gets. +func Get() (string, error) { + data, err := readFileFromKoData(commitIDFile) + if err != nil { + return "", err + } + commitID := strings.TrimSpace(string(data)) + if !commitIDRE.MatchString(commitID) { + err := fmt.Errorf("%q is not a valid GitHub commit ID", commitID) + return "", err + } + return string(commitID[0:7]), nil +} + +// readFileFromKoData tries to read data as string from the file with given name +// under KO_DATA_PATH then returns the content as string. The file is expected +// to be wrapped into the container from /kodata by ko. If it fails, returns +// the error it gets. +func readFileFromKoData(filename string) ([]byte, error) { + koDataPath := os.Getenv(koDataPathEnvName) + if koDataPath == "" { + err := fmt.Errorf("%q does not exist or is empty", koDataPathEnvName) + return nil, err + } + fullFilename := filepath.Join(koDataPath, filename) + return ioutil.ReadFile(fullFilename) +} diff --git a/changeset/commit_test.go b/changeset/commit_test.go new file mode 100644 index 000000000..105e39356 --- /dev/null +++ b/changeset/commit_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2018 The Knative 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 changeset + +import ( + "fmt" + "os" + "testing" +) + +const ( + nonCommittedHeadContext = "ref: refs/heads/non_committed_branch" + testCommitID = "a2d1bdf" +) + +func TestReadFile(t *testing.T) { + tests := []struct { + name string + koDataPath string + koDataPathEnvDoesNotExist bool + want string + wantErr bool + err error + }{{ + name: "committed branch", + koDataPath: "testdata", + want: testCommitID, + }, { + name: "non committed branch", + koDataPath: "testdata/noncommitted", + wantErr: true, + err: fmt.Errorf("%q is not a valid GitHub commit ID", nonCommittedHeadContext), + }, { + name: "KO_DATA_PATH is empty", + koDataPath: "", + wantErr: true, + err: fmt.Errorf("%q does not exist or is empty", koDataPathEnvName), + }, { + name: "KO_DATA_PATH does not exist", + koDataPath: "", + wantErr: true, + koDataPathEnvDoesNotExist: true, + err: fmt.Errorf("%q does not exist or is empty", koDataPathEnvName), + }, { + name: "HEAD file does not exist", + koDataPath: "testdata/nonexisting", + wantErr: true, + err: fmt.Errorf("open testdata/nonexisting/HEAD: no such file or directory"), + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.koDataPathEnvDoesNotExist { + os.Clearenv() + } else { + os.Setenv(koDataPathEnvName, test.koDataPath) + } + + got, err := Get() + + if (err != nil) != test.wantErr { + t.Errorf("Get() = %v", err) + } + if !test.wantErr { + if test.want != got { + t.Errorf("wanted %q but got %q", test.want, got) + } + } else { + if err == nil || err.Error() != test.err.Error() { + t.Errorf("wanted error %v but got: %v", test.err, err) + } + } + }) + } +} diff --git a/changeset/doc.go b/changeset/doc.go new file mode 100644 index 000000000..c56f7ebfb --- /dev/null +++ b/changeset/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2018 The Knative 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 changeset provides Knative utilities for fetching GitHub Commit ID +// from kodata directory. It requires GitHub HEAD file to be linked into +// Knative component source code via the following command: +// ln -s -r .git/HEAD ./cmd//kodata/ +// Then ko will build this file into $KO_DATA_PATH when building the container +// for a Knative component. +package changeset diff --git a/changeset/testdata/HEAD b/changeset/testdata/HEAD new file mode 100644 index 000000000..58b38ebae --- /dev/null +++ b/changeset/testdata/HEAD @@ -0,0 +1 @@ +a2d1bdfe929516d7da141aef68631a7ee6941b2d diff --git a/changeset/testdata/noncommitted/HEAD b/changeset/testdata/noncommitted/HEAD new file mode 100644 index 000000000..89fe2d356 --- /dev/null +++ b/changeset/testdata/noncommitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/non_committed_branch diff --git a/logging/config.go b/logging/config.go index 8c0dfe50d..c03c19314 100644 --- a/logging/config.go +++ b/logging/config.go @@ -25,6 +25,7 @@ import ( "go.uber.org/zap/zapcore" corev1 "k8s.io/api/core/v1" + "github.com/knative/pkg/changeset" "github.com/knative/pkg/logging/logkey" ) @@ -37,7 +38,7 @@ import ( func NewLogger(configJSON string, levelOverride string, opts ...zap.Option) (*zap.SugaredLogger, zap.AtomicLevel) { logger, atomicLevel, err := newLoggerFromConfig(configJSON, levelOverride, opts) if err == nil { - return logger.Sugar(), atomicLevel + return enrichLoggerWithCommitID(logger.Sugar()), atomicLevel } loggingCfg := zap.NewProductionConfig() @@ -51,7 +52,18 @@ func NewLogger(configJSON string, levelOverride string, opts ...zap.Option) (*za if err2 != nil { panic(err2) } - return logger.Named("fallback-logger").Sugar(), loggingCfg.Level + return enrichLoggerWithCommitID(logger.Named("fallback-logger").Sugar()), loggingCfg.Level +} + +func enrichLoggerWithCommitID(logger *zap.SugaredLogger) *zap.SugaredLogger { + commmitID, err := changeset.Get() + if err == nil { + // Enrich logs with GitHub commit ID. + return logger.With(zap.String(logkey.GitHubCommitID, commmitID)) + } + + logger.Warnf("Fetch GitHub commit ID from kodata failed: %v", err) + return logger } // NewLoggerFromConfig creates a logger using the provided Config diff --git a/logging/logkey/constants.go b/logging/logkey/constants.go index 809b10128..e4c62ee0d 100644 --- a/logging/logkey/constants.go +++ b/logging/logkey/constants.go @@ -55,4 +55,8 @@ const ( // KubernetesService is the key used to represent a Kubernetes service name in logs KubernetesService = "knative.dev/k8sservice" + + // GitHubCommitID is the key used to represent the GitHub Commit ID where the + // Knative component was built from in logs + GitHubCommitID = "commit" )