Worker: allow configuring executor CloudWatch group

We need the ability to use different CloudWatch group for the
osbuild-executor on Fedora workers in staging and production
environment.

Extend the worker confguration to allow configuring the CloudWatch group
name used by the osbuild-executor. Extend the secure instance code to
instruct cloud-init via user data to create /tmp/cloud_init_vars file
with the CloudWatch group name in the osbuild-executor instance, to make
it possible for the executor to configure its logging differently based
on the value.

Cover new changes by unit tests.

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
This commit is contained in:
Tomáš Hozza 2024-03-07 15:23:31 +01:00 committed by Tomáš Hozza
parent ceddabc395
commit e7743f17ec
7 changed files with 90 additions and 21 deletions

View file

@ -72,9 +72,10 @@ type pulpConfig struct {
}
type executorConfig struct {
Type string `toml:"type"`
IAMProfile string `toml:"iam_profile"`
KeyName string `toml:"key_name"`
Type string `toml:"type"`
IAMProfile string `toml:"iam_profile"`
KeyName string `toml:"key_name"`
CloudWatchGroup string `toml:"cloudwatch_group"`
}
type workerConfig struct {

View file

@ -69,12 +69,21 @@ offline_token = "/etc/osbuild-worker/offline_token"
[pulp]
credentials = "/etc/osbuild-worker/pulp-creds"
server_address = "https://example.com/pulp"
[osbuild_executor]
type = "aws.ec2"
iam_profile = "osbuild-worker"
key_name = "osbuild-worker"
cloudwatch_group = "osbuild-worker"
`,
want: &workerConfig{
BasePath: "/api/image-builder-worker/v1",
DNFJson: "/usr/libexec/osbuild-depsolve-dnf",
OSBuildExecutor: &executorConfig{
Type: "host",
Type: "aws.ec2",
IAMProfile: "osbuild-worker",
KeyName: "osbuild-worker",
CloudWatchGroup: "osbuild-worker",
},
Composer: &composerConfig{
Proxy: "http://proxy.example.com",

View file

@ -77,9 +77,10 @@ type PulpConfiguration struct {
}
type ExecutorConfiguration struct {
Type string
IAMProfile string
KeyName string
Type string
IAMProfile string
KeyName string
CloudWatchGroup string
}
type OSBuildJobImpl struct {
@ -489,7 +490,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
case "host":
executor = osbuildexecutor.NewHostExecutor()
case "aws.ec2":
executor = osbuildexecutor.NewAWSEC2Executor(impl.OSBuildExecutor.IAMProfile, impl.OSBuildExecutor.KeyName)
executor = osbuildexecutor.NewAWSEC2Executor(impl.OSBuildExecutor.IAMProfile, impl.OSBuildExecutor.KeyName, impl.OSBuildExecutor.CloudWatchGroup)
default:
osbuildJobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorInvalidConfig, "No osbuild executor defined", nil)
return err

View file

@ -474,9 +474,10 @@ func main() {
Store: store,
Output: output,
OSBuildExecutor: ExecutorConfiguration{
Type: config.OSBuildExecutor.Type,
IAMProfile: config.OSBuildExecutor.IAMProfile,
KeyName: config.OSBuildExecutor.KeyName,
Type: config.OSBuildExecutor.Type,
IAMProfile: config.OSBuildExecutor.IAMProfile,
KeyName: config.OSBuildExecutor.KeyName,
CloudWatchGroup: config.OSBuildExecutor.CloudWatchGroup,
},
KojiServers: kojiServers,
GCPConfig: gcpConfig,

View file

@ -18,15 +18,27 @@ type SecureInstance struct {
Instance *ec2.Instance
}
const UserData = `#cloud-config
// SecureInstanceUserData returns the cloud-init user data for a secure instance.
func SecureInstanceUserData(CloudWatchGroup string) string {
additionalFiles := ""
if CloudWatchGroup != "" {
additionalFiles += fmt.Sprintf(` - path: /tmp/cloud_init_vars
content: |
OSBUILD_EXECUTOR_CLOUDWATCH_GROUP='%s'
`, CloudWatchGroup)
}
return fmt.Sprintf(`#cloud-config
write_files:
- path: /tmp/worker-run-executor-service
content: ''
`
%s`, additionalFiles)
}
// Runs an instance with a security group that only allows traffic to
// the host. Will replace resources if they already exists.
func (a *AWS) RunSecureInstance(iamProfile, keyName string) (*SecureInstance, error) {
func (a *AWS) RunSecureInstance(iamProfile, keyName, CloudWatchGroup string) (*SecureInstance, error) {
identity, err := a.ec2metadata.GetInstanceIdentityDocument()
if err != nil {
logrus.Errorf("Error getting the identity document, %s", err)
@ -67,7 +79,7 @@ func (a *AWS) RunSecureInstance(iamProfile, keyName string) (*SecureInstance, er
return nil, err
}
ltID, err := a.createOrReplaceLT(identity.InstanceID, imageID, sgID, instanceType, iamProfile, keyName)
ltID, err := a.createOrReplaceLT(identity.InstanceID, imageID, sgID, instanceType, iamProfile, keyName, CloudWatchGroup)
if ltID != "" {
secureInstance.LTID = ltID
}
@ -284,7 +296,7 @@ func isLaunchTemplateNotFoundError(err error) bool {
}
func (a *AWS) createOrReplaceLT(hostInstanceID, imageID, sgID, instanceType, iamProfile, keyName string) (string, error) {
func (a *AWS) createOrReplaceLT(hostInstanceID, imageID, sgID, instanceType, iamProfile, keyName, CloudWatchGroup string) (string, error) {
ltName := fmt.Sprintf("launch-template-for-%s-runner-instance", hostInstanceID)
descrLTOutput, err := a.ec2.DescribeLaunchTemplates(&ec2.DescribeLaunchTemplatesInput{
LaunchTemplateNames: []*string{
@ -322,7 +334,7 @@ func (a *AWS) createOrReplaceLT(hostInstanceID, imageID, sgID, instanceType, iam
SecurityGroupIds: []*string{
aws.String(sgID),
},
UserData: aws.String(base64.StdEncoding.EncodeToString([]byte(UserData))),
UserData: aws.String(base64.StdEncoding.EncodeToString([]byte(SecureInstanceUserData(CloudWatchGroup)))),
},
TagSpecifications: []*ec2.TagSpecification{
&ec2.TagSpecification{

View file

@ -0,0 +1,43 @@
package awscloud
import (
"fmt"
"testing"
)
func TestSecureInstanceUserData(t *testing.T) {
type testCase struct {
CloudWatchGroup string
ExpectedUserData string
}
testCases := []testCase{
{
ExpectedUserData: `#cloud-config
write_files:
- path: /tmp/worker-run-executor-service
content: ''
`,
},
{
CloudWatchGroup: "test-group",
ExpectedUserData: `#cloud-config
write_files:
- path: /tmp/worker-run-executor-service
content: ''
- path: /tmp/cloud_init_vars
content: |
OSBUILD_EXECUTOR_CLOUDWATCH_GROUP='test-group'
`,
},
}
for idx, tc := range testCases {
t.Run(fmt.Sprintf("Test case %d", idx), func(t *testing.T) {
userData := SecureInstanceUserData(tc.CloudWatchGroup)
if userData != tc.ExpectedUserData {
t.Errorf("Expected: %s, got: %s", tc.ExpectedUserData, userData)
}
})
}
}

View file

@ -13,8 +13,9 @@ import (
)
type awsEC2Executor struct {
iamProfile string
keyName string
iamProfile string
keyName string
cloudWatchGroup string
}
func (ec2e *awsEC2Executor) RunOSBuild(manifest []byte, store, outputDirectory string, exports, exportPaths, checkpoints,
@ -29,7 +30,7 @@ func (ec2e *awsEC2Executor) RunOSBuild(manifest []byte, store, outputDirectory s
return nil, err
}
si, err := aws.RunSecureInstance(ec2e.iamProfile, ec2e.keyName)
si, err := aws.RunSecureInstance(ec2e.iamProfile, ec2e.keyName, ec2e.cloudWatchGroup)
if err != nil {
return nil, err
}
@ -92,9 +93,10 @@ func (ec2e *awsEC2Executor) RunOSBuild(manifest []byte, store, outputDirectory s
return &osbuildResult, nil
}
func NewAWSEC2Executor(iamProfile, keyName string) Executor {
func NewAWSEC2Executor(iamProfile, keyName, cloudWatchGroup string) Executor {
return &awsEC2Executor{
iamProfile,
keyName,
cloudWatchGroup,
}
}