diff --git a/agent/configs/configs.go b/agent/configs/configs.go index f29e8afcba..ae5fc8c18c 100644 --- a/agent/configs/configs.go +++ b/agent/configs/configs.go @@ -1,6 +1,7 @@ package configs import ( + "fmt" "sync" "github.com/docker/swarmkit/agent/exec" @@ -22,13 +23,13 @@ func NewManager() exec.ConfigsManager { } // Get returns a config by ID. If the config doesn't exist, returns nil. -func (r *configs) Get(configID string) *api.Config { +func (r *configs) Get(configID string) (*api.Config, error) { r.mu.RLock() defer r.mu.RUnlock() if r, ok := r.m[configID]; ok { - return r + return r, nil } - return nil + return nil, fmt.Errorf("config %s not found", configID) } // Add adds one or more configs to the config map. @@ -63,9 +64,9 @@ type taskRestrictedConfigsProvider struct { configIDs map[string]struct{} // allow list of config ids } -func (sp *taskRestrictedConfigsProvider) Get(configID string) *api.Config { +func (sp *taskRestrictedConfigsProvider) Get(configID string) (*api.Config, error) { if _, ok := sp.configIDs[configID]; !ok { - return nil + return nil, fmt.Errorf("task not authorized to access config %s", configID) } return sp.configs.Get(configID) diff --git a/agent/exec/executor.go b/agent/exec/executor.go index 12bf29b4d2..8c3fd03506 100644 --- a/agent/exec/executor.go +++ b/agent/exec/executor.go @@ -52,7 +52,7 @@ type DependencyGetter interface { type SecretGetter interface { // Get returns the the secret with a specific secret ID, if available. // When the secret is not available, the return will be nil. - Get(secretID string) *api.Secret + Get(secretID string) (*api.Secret, error) } // SecretsManager is the interface for secret storage and updates. @@ -68,7 +68,7 @@ type SecretsManager interface { type ConfigGetter interface { // Get returns the the config with a specific config ID, if available. // When the config is not available, the return will be nil. - Get(configID string) *api.Config + Get(configID string) (*api.Config, error) } // ConfigsManager is the interface for config storage and updates. diff --git a/agent/secrets/secrets.go b/agent/secrets/secrets.go index 2f68fa20f3..233101d0fb 100644 --- a/agent/secrets/secrets.go +++ b/agent/secrets/secrets.go @@ -1,6 +1,7 @@ package secrets import ( + "fmt" "sync" "github.com/docker/swarmkit/agent/exec" @@ -22,13 +23,13 @@ func NewManager() exec.SecretsManager { } // Get returns a secret by ID. If the secret doesn't exist, returns nil. -func (s *secrets) Get(secretID string) *api.Secret { +func (s *secrets) Get(secretID string) (*api.Secret, error) { s.mu.RLock() defer s.mu.RUnlock() if s, ok := s.m[secretID]; ok { - return s + return s, nil } - return nil + return nil, fmt.Errorf("secret %s not found", secretID) } // Add adds one or more secrets to the secret map. @@ -63,9 +64,9 @@ type taskRestrictedSecretsProvider struct { secretIDs map[string]struct{} // allow list of secret ids } -func (sp *taskRestrictedSecretsProvider) Get(secretID string) *api.Secret { +func (sp *taskRestrictedSecretsProvider) Get(secretID string) (*api.Secret, error) { if _, ok := sp.secretIDs[secretID]; !ok { - return nil + return nil, fmt.Errorf("task not authorized to access secret %s", secretID) } return sp.secrets.Get(secretID) diff --git a/agent/worker_test.go b/agent/worker_test.go index 3a2b87e69c..38a07991f5 100644 --- a/agent/worker_test.go +++ b/agent/worker_test.go @@ -186,10 +186,14 @@ func TestWorkerAssign(t *testing.T) { assert.Equal(t, testcase.expectedTasks, tasks) assert.Equal(t, testcase.expectedAssigned, assigned) for _, secret := range testcase.expectedSecrets { - assert.NotNil(t, executor.Secrets().Get(secret.ID)) + secret, err := executor.Secrets().Get(secret.ID) + assert.NoError(t, err) + assert.NotNil(t, secret) } for _, config := range testcase.expectedConfigs { - assert.NotNil(t, executor.Configs().Get(config.ID)) + config, err := executor.Configs().Get(config.ID) + assert.NoError(t, err) + assert.NotNil(t, config) } } } @@ -280,10 +284,14 @@ func TestWorkerWait(t *testing.T) { assert.Equal(t, expectedTasks, tasks) assert.Equal(t, expectedAssigned, assigned) for _, secret := range expectedSecrets { - assert.NotNil(t, executor.Secrets().Get(secret.ID)) + secret, err := executor.Secrets().Get(secret.ID) + assert.NoError(t, err) + assert.NotNil(t, secret) } for _, config := range expectedConfigs { - assert.NotNil(t, executor.Configs().Get(config.ID)) + config, err := executor.Configs().Get(config.ID) + assert.NoError(t, err) + assert.NotNil(t, config) } err := worker.Assign(ctx, nil) @@ -573,10 +581,14 @@ func TestWorkerUpdate(t *testing.T) { assert.Equal(t, testcase.expectedTasks, tasks) assert.Equal(t, testcase.expectedAssigned, assigned) for _, secret := range testcase.expectedSecrets { - assert.NotNil(t, executor.Secrets().Get(secret.ID)) + secret, err := executor.Secrets().Get(secret.ID) + assert.NoError(t, err) + assert.NotNil(t, secret) } for _, config := range testcase.expectedConfigs { - assert.NotNil(t, executor.Configs().Get(config.ID)) + config, err := executor.Configs().Get(config.ID) + assert.NoError(t, err) + assert.NotNil(t, config) } } } diff --git a/api/specs.pb.go b/api/specs.pb.go index 39b0c0abf0..8578cf3849 100644 --- a/api/specs.pb.go +++ b/api/specs.pb.go @@ -760,6 +760,12 @@ type SecretSpec struct { Annotations Annotations `protobuf:"bytes,1,opt,name=annotations" json:"annotations"` // Data is the secret payload - the maximum size is 500KB (that is, 500*1024 bytes) Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + // Templating controls whether and how to evaluate the secret payload as + // a template. If it is not set, no templating is used. + // + // The currently recognized values are: + // - golang: Go templating + Templating *Driver `protobuf:"bytes,3,opt,name=templating" json:"templating,omitempty"` } func (m *SecretSpec) Reset() { *m = SecretSpec{} } @@ -773,6 +779,12 @@ type ConfigSpec struct { // TODO(aaronl): Do we want to revise this to include multiple payloads in a single // ConfigSpec? Define this to be a tar? etc... Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + // Templating controls whether and how to evaluate the secret payload as + // a template. If it is not set, no templating is used. + // + // The currently recognized values are: + // - golang: Go templating + Templating *Driver `protobuf:"bytes,3,opt,name=templating" json:"templating,omitempty"` } func (m *ConfigSpec) Reset() { *m = ConfigSpec{} } @@ -1224,6 +1236,10 @@ func (m *SecretSpec) CopyFrom(src interface{}) { m.Data = make([]byte, len(o.Data)) copy(m.Data, o.Data) } + if o.Templating != nil { + m.Templating = &Driver{} + github_com_docker_swarmkit_api_deepcopy.Copy(m.Templating, o.Templating) + } } func (m *ConfigSpec) Copy() *ConfigSpec { @@ -1244,6 +1260,10 @@ func (m *ConfigSpec) CopyFrom(src interface{}) { m.Data = make([]byte, len(o.Data)) copy(m.Data, o.Data) } + if o.Templating != nil { + m.Templating = &Driver{} + github_com_docker_swarmkit_api_deepcopy.Copy(m.Templating, o.Templating) + } } func (m *NodeSpec) Marshal() (dAtA []byte, err error) { @@ -2227,6 +2247,16 @@ func (m *SecretSpec) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintSpecs(dAtA, i, uint64(len(m.Data))) i += copy(dAtA[i:], m.Data) } + if m.Templating != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintSpecs(dAtA, i, uint64(m.Templating.Size())) + n37, err := m.Templating.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n37 + } return i, nil } @@ -2248,17 +2278,27 @@ func (m *ConfigSpec) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintSpecs(dAtA, i, uint64(m.Annotations.Size())) - n37, err := m.Annotations.MarshalTo(dAtA[i:]) + n38, err := m.Annotations.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n37 + i += n38 if len(m.Data) > 0 { dAtA[i] = 0x12 i++ i = encodeVarintSpecs(dAtA, i, uint64(len(m.Data))) i += copy(dAtA[i:], m.Data) } + if m.Templating != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintSpecs(dAtA, i, uint64(m.Templating.Size())) + n39, err := m.Templating.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n39 + } return i, nil } @@ -2685,6 +2725,10 @@ func (m *SecretSpec) Size() (n int) { if l > 0 { n += 1 + l + sovSpecs(uint64(l)) } + if m.Templating != nil { + l = m.Templating.Size() + n += 1 + l + sovSpecs(uint64(l)) + } return n } @@ -2697,6 +2741,10 @@ func (m *ConfigSpec) Size() (n int) { if l > 0 { n += 1 + l + sovSpecs(uint64(l)) } + if m.Templating != nil { + l = m.Templating.Size() + n += 1 + l + sovSpecs(uint64(l)) + } return n } @@ -2973,6 +3021,7 @@ func (this *SecretSpec) String() string { s := strings.Join([]string{`&SecretSpec{`, `Annotations:` + strings.Replace(strings.Replace(this.Annotations.String(), "Annotations", "Annotations", 1), `&`, ``, 1) + `,`, `Data:` + fmt.Sprintf("%v", this.Data) + `,`, + `Templating:` + strings.Replace(fmt.Sprintf("%v", this.Templating), "Driver", "Driver", 1) + `,`, `}`, }, "") return s @@ -2984,6 +3033,7 @@ func (this *ConfigSpec) String() string { s := strings.Join([]string{`&ConfigSpec{`, `Annotations:` + strings.Replace(strings.Replace(this.Annotations.String(), "Annotations", "Annotations", 1), `&`, ``, 1) + `,`, `Data:` + fmt.Sprintf("%v", this.Data) + `,`, + `Templating:` + strings.Replace(fmt.Sprintf("%v", this.Templating), "Driver", "Driver", 1) + `,`, `}`, }, "") return s @@ -5800,6 +5850,39 @@ func (m *SecretSpec) Unmarshal(dAtA []byte) error { m.Data = []byte{} } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Templating", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSpecs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSpecs + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Templating == nil { + m.Templating = &Driver{} + } + if err := m.Templating.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipSpecs(dAtA[iNdEx:]) @@ -5911,6 +5994,39 @@ func (m *ConfigSpec) Unmarshal(dAtA []byte) error { m.Data = []byte{} } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Templating", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSpecs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSpecs + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Templating == nil { + m.Templating = &Driver{} + } + if err := m.Templating.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipSpecs(dAtA[iNdEx:]) @@ -6040,121 +6156,122 @@ var ( func init() { proto.RegisterFile("specs.proto", fileDescriptorSpecs) } var fileDescriptorSpecs = []byte{ - // 1846 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xcf, 0x73, 0x1b, 0x49, - 0x15, 0xb6, 0x6c, 0x59, 0x3f, 0xde, 0xc8, 0x89, 0xd2, 0x24, 0x61, 0xac, 0xb0, 0xb2, 0xa2, 0x0d, - 0xc1, 0xcb, 0x16, 0x72, 0x61, 0xa8, 0x25, 0x4b, 0x58, 0x40, 0xb2, 0x84, 0x63, 0x8c, 0x1d, 0x55, - 0xdb, 0x1b, 0xc8, 0x49, 0xd5, 0x9e, 0x69, 0x4b, 0x53, 0x1e, 0x75, 0x0f, 0xdd, 0x3d, 0xda, 0xd2, - 0x8d, 0xe3, 0x56, 0xae, 0x9c, 0x5d, 0x1c, 0xf8, 0x67, 0x72, 0xa4, 0x38, 0x71, 0x72, 0xb1, 0xfe, - 0x17, 0xb8, 0x71, 0x81, 0xea, 0x9e, 0x1e, 0xfd, 0x48, 0xc6, 0x9b, 0x54, 0x11, 0x6e, 0xdd, 0xaf, - 0xbf, 0xef, 0x75, 0xf7, 0xeb, 0xaf, 0xfb, 0xbd, 0x06, 0x47, 0x46, 0xd4, 0x93, 0xad, 0x48, 0x70, - 0xc5, 0x11, 0xf2, 0xb9, 0x77, 0x41, 0x45, 0x4b, 0x7e, 0x45, 0xc4, 0xf8, 0x22, 0x50, 0xad, 0xc9, - 0x8f, 0x6b, 0x8e, 0x9a, 0x46, 0xd4, 0x02, 0x6a, 0x77, 0x87, 0x7c, 0xc8, 0x4d, 0x73, 0x47, 0xb7, - 0xac, 0xb5, 0x3e, 0xe4, 0x7c, 0x18, 0xd2, 0x1d, 0xd3, 0x3b, 0x8b, 0xcf, 0x77, 0xfc, 0x58, 0x10, - 0x15, 0x70, 0x66, 0xc7, 0x37, 0xdf, 0x1c, 0x27, 0x6c, 0x9a, 0x0c, 0x35, 0x2f, 0xf3, 0x50, 0x3a, - 0xe6, 0x3e, 0x3d, 0x89, 0xa8, 0x87, 0xf6, 0xc1, 0x21, 0x8c, 0x71, 0x65, 0xb8, 0xd2, 0xcd, 0x35, - 0x72, 0xdb, 0xce, 0xee, 0x56, 0xeb, 0xed, 0x45, 0xb5, 0xda, 0x73, 0x58, 0x27, 0xff, 0xfa, 0x6a, - 0x6b, 0x05, 0x2f, 0x32, 0xd1, 0xaf, 0xa0, 0xe2, 0x53, 0x19, 0x08, 0xea, 0x0f, 0x04, 0x0f, 0xa9, - 0xbb, 0xda, 0xc8, 0x6d, 0xdf, 0xda, 0xfd, 0x5e, 0x96, 0x27, 0x3d, 0x39, 0xe6, 0x21, 0xc5, 0x8e, - 0x65, 0xe8, 0x0e, 0xda, 0x07, 0x18, 0xd3, 0xf1, 0x19, 0x15, 0x72, 0x14, 0x44, 0xee, 0x9a, 0xa1, - 0xff, 0xe0, 0x26, 0xba, 0x5e, 0x7b, 0xeb, 0x68, 0x06, 0xc7, 0x0b, 0x54, 0x74, 0x04, 0x15, 0x32, - 0x21, 0x41, 0x48, 0xce, 0x82, 0x30, 0x50, 0x53, 0x37, 0x6f, 0x5c, 0x7d, 0xf2, 0xad, 0xae, 0xda, - 0x0b, 0x04, 0xbc, 0x44, 0x6f, 0xfa, 0x00, 0xf3, 0x89, 0xd0, 0x63, 0x28, 0xf6, 0x7b, 0xc7, 0xdd, - 0x83, 0xe3, 0xfd, 0xea, 0x4a, 0x6d, 0xf3, 0xd5, 0x65, 0xe3, 0x9e, 0xf6, 0x31, 0x07, 0xf4, 0x29, - 0xf3, 0x03, 0x36, 0x44, 0xdb, 0x50, 0x6a, 0xef, 0xed, 0xf5, 0xfa, 0xa7, 0xbd, 0x6e, 0x35, 0x57, - 0xab, 0xbd, 0xba, 0x6c, 0xdc, 0x5f, 0x06, 0xb6, 0x3d, 0x8f, 0x46, 0x8a, 0xfa, 0xb5, 0xfc, 0xd7, - 0x7f, 0xad, 0xaf, 0x34, 0xbf, 0xce, 0x41, 0x65, 0x71, 0x11, 0xe8, 0x31, 0x14, 0xda, 0x7b, 0xa7, - 0x07, 0x2f, 0x7a, 0xd5, 0x95, 0x39, 0x7d, 0x11, 0xd1, 0xf6, 0x54, 0x30, 0xa1, 0xe8, 0x11, 0xac, - 0xf7, 0xdb, 0x5f, 0x9e, 0xf4, 0xaa, 0xb9, 0xf9, 0x72, 0x16, 0x61, 0x7d, 0x12, 0x4b, 0x83, 0xea, - 0xe2, 0xf6, 0xc1, 0x71, 0x75, 0x35, 0x1b, 0xd5, 0x15, 0x24, 0x60, 0x76, 0x29, 0x7f, 0xc9, 0x83, - 0x73, 0x42, 0xc5, 0x24, 0xf0, 0x3e, 0xb0, 0x44, 0x3e, 0x83, 0xbc, 0x22, 0xf2, 0xc2, 0x48, 0xc3, - 0xc9, 0x96, 0xc6, 0x29, 0x91, 0x17, 0x7a, 0x52, 0x4b, 0x37, 0x78, 0xad, 0x0c, 0x41, 0xa3, 0x30, - 0xf0, 0x88, 0xa2, 0xbe, 0x51, 0x86, 0xb3, 0xfb, 0xfd, 0x2c, 0x36, 0x9e, 0xa1, 0xec, 0xfa, 0x9f, - 0xad, 0xe0, 0x05, 0x2a, 0x7a, 0x0a, 0x85, 0x61, 0xc8, 0xcf, 0x48, 0x68, 0x34, 0xe1, 0xec, 0x3e, - 0xcc, 0x72, 0xb2, 0x6f, 0x10, 0x73, 0x07, 0x96, 0x82, 0x9e, 0x40, 0x21, 0x8e, 0x7c, 0xa2, 0xa8, - 0x5b, 0x30, 0xe4, 0x46, 0x16, 0xf9, 0x4b, 0x83, 0xd8, 0xe3, 0xec, 0x3c, 0x18, 0x62, 0x8b, 0x47, - 0x87, 0x50, 0x62, 0x54, 0x7d, 0xc5, 0xc5, 0x85, 0x74, 0x8b, 0x8d, 0xb5, 0x6d, 0x67, 0xf7, 0xd3, - 0x4c, 0x31, 0x26, 0x98, 0xb6, 0x52, 0xc4, 0x1b, 0x8d, 0x29, 0x53, 0x89, 0x9b, 0xce, 0xaa, 0x9b, - 0xc3, 0x33, 0x07, 0xe8, 0x17, 0x50, 0xa2, 0xcc, 0x8f, 0x78, 0xc0, 0x94, 0x5b, 0xba, 0x79, 0x21, - 0x3d, 0x8b, 0xd1, 0xc1, 0xc4, 0x33, 0x86, 0x66, 0x0b, 0x1e, 0x86, 0x67, 0xc4, 0xbb, 0x70, 0xcb, - 0xef, 0xb9, 0x8d, 0x19, 0xa3, 0x53, 0x80, 0xfc, 0x98, 0xfb, 0xb4, 0xb9, 0x03, 0x77, 0xde, 0x0a, - 0x35, 0xaa, 0x41, 0xc9, 0x86, 0x3a, 0xd1, 0x48, 0x1e, 0xcf, 0xfa, 0xcd, 0xdb, 0xb0, 0xb1, 0x14, - 0xd6, 0xe6, 0xdf, 0xf3, 0x50, 0x4a, 0xcf, 0x1a, 0xb5, 0xa1, 0xec, 0x71, 0xa6, 0x48, 0xc0, 0xa8, - 0xb0, 0xf2, 0xca, 0x3c, 0x99, 0xbd, 0x14, 0xa4, 0x59, 0xcf, 0x56, 0xf0, 0x9c, 0x85, 0x7e, 0x03, - 0x65, 0x41, 0x25, 0x8f, 0x85, 0x47, 0xa5, 0xd5, 0xd7, 0x76, 0xb6, 0x42, 0x12, 0x10, 0xa6, 0x7f, - 0x8c, 0x03, 0x41, 0x75, 0x94, 0x25, 0x9e, 0x53, 0xd1, 0x53, 0x28, 0x0a, 0x2a, 0x15, 0x11, 0xea, - 0xdb, 0x24, 0x82, 0x13, 0x48, 0x9f, 0x87, 0x81, 0x37, 0xc5, 0x29, 0x03, 0x3d, 0x85, 0x72, 0x14, - 0x12, 0xcf, 0x78, 0x75, 0xd7, 0x0d, 0xfd, 0xa3, 0x2c, 0x7a, 0x3f, 0x05, 0xe1, 0x39, 0x1e, 0x7d, - 0x0e, 0x10, 0xf2, 0xe1, 0xc0, 0x17, 0xc1, 0x84, 0x0a, 0x2b, 0xb1, 0x5a, 0x16, 0xbb, 0x6b, 0x10, - 0xb8, 0x1c, 0xf2, 0x61, 0xd2, 0x44, 0xfb, 0xff, 0x93, 0xbe, 0x16, 0xb4, 0x75, 0x08, 0x40, 0x66, - 0xa3, 0x56, 0x5d, 0x9f, 0xbc, 0x97, 0x2b, 0x7b, 0x22, 0x0b, 0x74, 0xf4, 0x10, 0x2a, 0xe7, 0x5c, - 0x78, 0x74, 0x60, 0x6f, 0x4d, 0xd9, 0x68, 0xc2, 0x31, 0xb6, 0x44, 0x5f, 0xa8, 0x03, 0xc5, 0x21, - 0x65, 0x54, 0x04, 0x9e, 0x0b, 0x66, 0xb2, 0xc7, 0x99, 0x17, 0x32, 0x81, 0xe0, 0x98, 0xa9, 0x60, - 0x4c, 0xed, 0x4c, 0x29, 0xb1, 0x53, 0x86, 0xa2, 0x48, 0x46, 0x9a, 0x7f, 0x00, 0xf4, 0x36, 0x16, - 0x21, 0xc8, 0x5f, 0x04, 0xcc, 0x37, 0xc2, 0x2a, 0x63, 0xd3, 0x46, 0x2d, 0x28, 0x46, 0x64, 0x1a, - 0x72, 0xe2, 0x5b, 0xb1, 0xdc, 0x6d, 0x25, 0xf9, 0xb2, 0x95, 0xe6, 0xcb, 0x56, 0x9b, 0x4d, 0x71, - 0x0a, 0x6a, 0x1e, 0xc2, 0xbd, 0xcc, 0x2d, 0xa3, 0x5d, 0xa8, 0xcc, 0x44, 0x38, 0x08, 0xec, 0x24, - 0x9d, 0xdb, 0xd7, 0x57, 0x5b, 0xce, 0x4c, 0xad, 0x07, 0x5d, 0xec, 0xcc, 0x40, 0x07, 0x7e, 0xf3, - 0xcf, 0x65, 0xd8, 0x58, 0x92, 0x32, 0xba, 0x0b, 0xeb, 0xc1, 0x98, 0x0c, 0xa9, 0x5d, 0x63, 0xd2, - 0x41, 0x3d, 0x28, 0x84, 0xe4, 0x8c, 0x86, 0x5a, 0xd0, 0xfa, 0x50, 0x7f, 0xf4, 0xce, 0x3b, 0xd1, - 0xfa, 0x9d, 0xc1, 0xf7, 0x98, 0x12, 0x53, 0x6c, 0xc9, 0xc8, 0x85, 0xa2, 0xc7, 0xc7, 0x63, 0xc2, - 0xf4, 0xd3, 0xb9, 0xb6, 0x5d, 0xc6, 0x69, 0x57, 0x47, 0x86, 0x88, 0xa1, 0x74, 0xf3, 0xc6, 0x6c, - 0xda, 0xa8, 0x0a, 0x6b, 0x94, 0x4d, 0xdc, 0x75, 0x63, 0xd2, 0x4d, 0x6d, 0xf1, 0x83, 0x44, 0x91, - 0x65, 0xac, 0x9b, 0x9a, 0x17, 0x4b, 0x2a, 0xdc, 0x62, 0x12, 0x51, 0xdd, 0x46, 0x3f, 0x83, 0xc2, - 0x98, 0xc7, 0x4c, 0x49, 0xb7, 0x64, 0x16, 0xbb, 0x99, 0xb5, 0xd8, 0x23, 0x8d, 0xb0, 0x4f, 0xbb, - 0x85, 0xa3, 0x1e, 0xdc, 0x91, 0x8a, 0x47, 0x83, 0xa1, 0x20, 0x1e, 0x1d, 0x44, 0x54, 0x04, 0xdc, - 0xb7, 0x4f, 0xd3, 0xe6, 0x5b, 0x87, 0xd2, 0xb5, 0x45, 0x0e, 0xbe, 0xad, 0x39, 0xfb, 0x9a, 0xd2, - 0x37, 0x0c, 0xd4, 0x87, 0x4a, 0x14, 0x87, 0xe1, 0x80, 0x47, 0x49, 0x96, 0x4a, 0xf4, 0xf4, 0x1e, - 0x21, 0xeb, 0xc7, 0x61, 0xf8, 0x3c, 0x21, 0x61, 0x27, 0x9a, 0x77, 0xd0, 0x7d, 0x28, 0x0c, 0x05, - 0x8f, 0x23, 0xe9, 0x3a, 0x26, 0x18, 0xb6, 0x87, 0xbe, 0x80, 0xa2, 0xa4, 0x9e, 0xa0, 0x4a, 0xba, - 0x15, 0xb3, 0xd5, 0x8f, 0xb3, 0x26, 0x39, 0x31, 0x10, 0x4c, 0xcf, 0xa9, 0xa0, 0xcc, 0xa3, 0x38, - 0xe5, 0xa0, 0x4d, 0x58, 0x53, 0x6a, 0xea, 0x6e, 0x34, 0x72, 0xdb, 0xa5, 0x4e, 0xf1, 0xfa, 0x6a, - 0x6b, 0xed, 0xf4, 0xf4, 0x25, 0xd6, 0x36, 0xfd, 0x82, 0x8e, 0xb8, 0x54, 0x8c, 0x8c, 0xa9, 0x7b, - 0xcb, 0xc4, 0x76, 0xd6, 0x47, 0x2f, 0x01, 0x7c, 0x26, 0x07, 0x9e, 0xb9, 0xb2, 0xee, 0x6d, 0xb3, - 0xbb, 0x4f, 0xdf, 0xbd, 0xbb, 0xee, 0xf1, 0x89, 0xcd, 0x22, 0x1b, 0xd7, 0x57, 0x5b, 0xe5, 0x59, - 0x17, 0x97, 0x7d, 0x26, 0x93, 0x26, 0xea, 0x80, 0x33, 0xa2, 0x24, 0x54, 0x23, 0x6f, 0x44, 0xbd, - 0x0b, 0xb7, 0x7a, 0x73, 0x5a, 0x78, 0x66, 0x60, 0xd6, 0xc3, 0x22, 0x49, 0x2b, 0x58, 0x2f, 0x55, - 0xba, 0x77, 0x4c, 0xac, 0x92, 0x0e, 0xfa, 0x08, 0x80, 0x47, 0x94, 0x0d, 0xa4, 0xf2, 0x03, 0xe6, - 0x22, 0xbd, 0x65, 0x5c, 0xd6, 0x96, 0x13, 0x6d, 0x40, 0x0f, 0xf4, 0xa3, 0x4d, 0xfc, 0x01, 0x67, - 0xe1, 0xd4, 0xfd, 0x8e, 0x19, 0x2d, 0x69, 0xc3, 0x73, 0x16, 0x4e, 0xd1, 0x16, 0x38, 0x46, 0x17, - 0x32, 0x18, 0x32, 0x12, 0xba, 0x77, 0x4d, 0x3c, 0x40, 0x9b, 0x4e, 0x8c, 0x45, 0x9f, 0x43, 0x12, - 0x0d, 0xe9, 0xde, 0xbb, 0xf9, 0x1c, 0xec, 0x62, 0xe7, 0xe7, 0x60, 0x39, 0xe8, 0x97, 0x00, 0x91, - 0x08, 0x26, 0x41, 0x48, 0x87, 0x54, 0xba, 0xf7, 0xcd, 0xa6, 0xeb, 0x99, 0xaf, 0xf5, 0x0c, 0x85, - 0x17, 0x18, 0xb5, 0xcf, 0xc1, 0x59, 0xb8, 0x6d, 0xfa, 0x96, 0x5c, 0xd0, 0xa9, 0xbd, 0xc0, 0xba, - 0xa9, 0x43, 0x32, 0x21, 0x61, 0x9c, 0x54, 0xc2, 0x65, 0x9c, 0x74, 0x7e, 0xbe, 0xfa, 0x24, 0x57, - 0xdb, 0x05, 0x67, 0x41, 0x75, 0xe8, 0x63, 0xd8, 0x10, 0x74, 0x18, 0x48, 0x25, 0xa6, 0x03, 0x12, - 0xab, 0x91, 0xfb, 0x6b, 0x43, 0xa8, 0xa4, 0xc6, 0x76, 0xac, 0x46, 0xb5, 0x01, 0xcc, 0x0f, 0x0f, - 0x35, 0xc0, 0xd1, 0xa2, 0x90, 0x54, 0x4c, 0xa8, 0xd0, 0xd9, 0x56, 0xc7, 0x7c, 0xd1, 0xa4, 0xc5, - 0x2b, 0x29, 0x11, 0xde, 0xc8, 0xbc, 0x1d, 0x65, 0x6c, 0x7b, 0xfa, 0x31, 0x48, 0x6f, 0x88, 0x7d, - 0x0c, 0x6c, 0xb7, 0xf9, 0xaf, 0x1c, 0x54, 0x16, 0x8b, 0x06, 0xb4, 0x97, 0x24, 0x7b, 0xb3, 0xa5, - 0x5b, 0xbb, 0x3b, 0xef, 0x2a, 0x32, 0x4c, 0x6a, 0x0d, 0x63, 0xed, 0xec, 0x48, 0xd7, 0xf7, 0x86, - 0x8c, 0x7e, 0x0a, 0xeb, 0x11, 0x17, 0x2a, 0x7d, 0xc2, 0xb2, 0x03, 0xcc, 0x45, 0x9a, 0x8a, 0x12, - 0x70, 0x73, 0x04, 0xb7, 0x96, 0xbd, 0xa1, 0x47, 0xb0, 0xf6, 0xe2, 0xa0, 0x5f, 0x5d, 0xa9, 0x3d, - 0x78, 0x75, 0xd9, 0xf8, 0xee, 0xf2, 0xe0, 0x8b, 0x40, 0xa8, 0x98, 0x84, 0x07, 0x7d, 0xf4, 0x43, - 0x58, 0xef, 0x1e, 0x9f, 0x60, 0x5c, 0xcd, 0xd5, 0xb6, 0x5e, 0x5d, 0x36, 0x1e, 0x2c, 0xe3, 0xf4, - 0x10, 0x8f, 0x99, 0x8f, 0xf9, 0xd9, 0xac, 0xd6, 0xfd, 0xf7, 0x2a, 0x38, 0xf6, 0x65, 0xff, 0xd0, - 0xdf, 0xa1, 0x8d, 0x24, 0x95, 0xa7, 0x57, 0x76, 0xf5, 0x9d, 0x19, 0xbd, 0x92, 0x10, 0xec, 0x19, - 0x3f, 0x84, 0x4a, 0x10, 0x4d, 0x3e, 0x1b, 0x50, 0x46, 0xce, 0x42, 0x5b, 0xf6, 0x96, 0xb0, 0xa3, - 0x6d, 0xbd, 0xc4, 0xa4, 0xdf, 0x8b, 0x80, 0x29, 0x2a, 0x98, 0x2d, 0x68, 0x4b, 0x78, 0xd6, 0x47, - 0x5f, 0x40, 0x3e, 0x88, 0xc8, 0xd8, 0x96, 0x21, 0x99, 0x3b, 0x38, 0xe8, 0xb7, 0x8f, 0xac, 0x06, - 0x3b, 0xa5, 0xeb, 0xab, 0xad, 0xbc, 0x36, 0x60, 0x43, 0x43, 0xf5, 0xb4, 0x12, 0xd0, 0x33, 0x99, - 0xb7, 0xbf, 0x84, 0x17, 0x2c, 0x5a, 0x47, 0x01, 0x1b, 0x0a, 0x2a, 0xa5, 0xc9, 0x02, 0x25, 0x9c, - 0x76, 0x51, 0x0d, 0x8a, 0xb6, 0x9e, 0x30, 0x05, 0x44, 0x59, 0xe7, 0x6a, 0x6b, 0xe8, 0x6c, 0x80, - 0x93, 0x44, 0x63, 0x70, 0x2e, 0xf8, 0xb8, 0xf9, 0x9f, 0x3c, 0x38, 0x7b, 0x61, 0x2c, 0x95, 0x4d, - 0x83, 0x1f, 0x2c, 0xf8, 0x2f, 0xe1, 0x0e, 0x31, 0xdf, 0x2b, 0xc2, 0x74, 0x4e, 0x31, 0x65, 0x9a, - 0x3d, 0x80, 0x47, 0x99, 0xee, 0x66, 0xe0, 0xa4, 0xa4, 0xeb, 0x14, 0xb4, 0x4f, 0x37, 0x87, 0xab, - 0xe4, 0x8d, 0x11, 0x74, 0x02, 0x1b, 0x5c, 0x78, 0x23, 0x2a, 0x55, 0x92, 0x89, 0xec, 0x77, 0x24, - 0xf3, 0xa3, 0xfa, 0x7c, 0x11, 0x68, 0x9f, 0xe1, 0x64, 0xb5, 0xcb, 0x3e, 0xd0, 0x13, 0xc8, 0x0b, - 0x72, 0x9e, 0x96, 0x9c, 0x99, 0x97, 0x04, 0x93, 0x73, 0xb5, 0xe4, 0xc2, 0x30, 0xd0, 0x6f, 0x01, - 0xfc, 0x40, 0x46, 0x44, 0x79, 0x23, 0x2a, 0xec, 0x61, 0x67, 0x6e, 0xb1, 0x3b, 0x43, 0x2d, 0x79, - 0x59, 0x60, 0xa3, 0x43, 0x28, 0x7b, 0x24, 0x95, 0x6b, 0xe1, 0xe6, 0x3f, 0xda, 0x5e, 0xdb, 0xba, - 0xa8, 0x6a, 0x17, 0xd7, 0x57, 0x5b, 0xa5, 0xd4, 0x82, 0x4b, 0x1e, 0xb1, 0xf2, 0x3d, 0x84, 0x0d, - 0xfd, 0x77, 0x1b, 0xf8, 0xf4, 0x9c, 0xc4, 0xa1, 0x4a, 0x64, 0x72, 0x43, 0x5a, 0xd1, 0x1f, 0x81, - 0xae, 0xc5, 0xd9, 0x75, 0x55, 0xd4, 0x82, 0x0d, 0xfd, 0x1e, 0xee, 0x50, 0xe6, 0x89, 0xa9, 0x11, - 0x6b, 0xba, 0xc2, 0xd2, 0xcd, 0x9b, 0xed, 0xcd, 0xc0, 0x4b, 0x9b, 0xad, 0xd2, 0x37, 0xec, 0xcd, - 0x00, 0x20, 0x49, 0xd4, 0x1f, 0x56, 0x7f, 0x08, 0xf2, 0x3e, 0x51, 0xc4, 0x48, 0xae, 0x82, 0x4d, - 0x5b, 0x4f, 0x95, 0x4c, 0xfa, 0x7f, 0x9f, 0xaa, 0xe3, 0xbe, 0xfe, 0xa6, 0xbe, 0xf2, 0x8f, 0x6f, - 0xea, 0x2b, 0x7f, 0xba, 0xae, 0xe7, 0x5e, 0x5f, 0xd7, 0x73, 0x7f, 0xbb, 0xae, 0xe7, 0xfe, 0x79, - 0x5d, 0xcf, 0x9d, 0x15, 0x4c, 0x25, 0xf5, 0x93, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xb3, 0x21, - 0x2b, 0x33, 0x82, 0x12, 0x00, 0x00, + // 1867 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0xcf, 0x73, 0x1b, 0x49, + 0x15, 0xb6, 0x6c, 0x59, 0x3f, 0xde, 0xc8, 0x89, 0xd2, 0x24, 0x61, 0xa2, 0xb0, 0xb2, 0xa2, 0x0d, + 0xc1, 0xcb, 0x16, 0x72, 0x61, 0xa8, 0x25, 0xbb, 0x61, 0x01, 0xc9, 0x12, 0x8e, 0x31, 0x76, 0x54, + 0x6d, 0x6f, 0x20, 0x27, 0x55, 0x7b, 0xa6, 0x3d, 0x9a, 0xf2, 0xa8, 0x7b, 0xe8, 0xe9, 0xd1, 0x96, + 0x6e, 0x1c, 0xb7, 0x72, 0xe5, 0xec, 0xe2, 0x40, 0xf1, 0xbf, 0xe4, 0x48, 0x71, 0xe2, 0xe4, 0x62, + 0xfd, 0x2f, 0x70, 0xe3, 0x02, 0xd5, 0x3d, 0x3d, 0xd2, 0x28, 0x19, 0x27, 0xa9, 0x22, 0x07, 0x6e, + 0xdd, 0xaf, 0xbf, 0xef, 0xcd, 0xeb, 0xd7, 0x5f, 0xf7, 0x7b, 0x03, 0x56, 0x14, 0x52, 0x27, 0xea, + 0x84, 0x82, 0x4b, 0x8e, 0x90, 0xcb, 0x9d, 0x73, 0x2a, 0x3a, 0xd1, 0xd7, 0x44, 0x4c, 0xce, 0x7d, + 0xd9, 0x99, 0xfe, 0xb8, 0x61, 0xc9, 0x59, 0x48, 0x0d, 0xa0, 0x71, 0xdb, 0xe3, 0x1e, 0xd7, 0xc3, + 0x6d, 0x35, 0x32, 0xd6, 0xa6, 0xc7, 0xb9, 0x17, 0xd0, 0x6d, 0x3d, 0x3b, 0x8d, 0xcf, 0xb6, 0xdd, + 0x58, 0x10, 0xe9, 0x73, 0x66, 0xd6, 0xef, 0xbd, 0xbe, 0x4e, 0xd8, 0x2c, 0x59, 0x6a, 0x5f, 0x14, + 0xa1, 0x72, 0xc4, 0x5d, 0x7a, 0x1c, 0x52, 0x07, 0xed, 0x81, 0x45, 0x18, 0xe3, 0x52, 0x73, 0x23, + 0xbb, 0xd0, 0x2a, 0x6c, 0x59, 0x3b, 0x9b, 0x9d, 0x37, 0x83, 0xea, 0x74, 0x17, 0xb0, 0x5e, 0xf1, + 0xd5, 0xe5, 0xe6, 0x0a, 0xce, 0x32, 0xd1, 0x2f, 0xa1, 0xe6, 0xd2, 0xc8, 0x17, 0xd4, 0x1d, 0x09, + 0x1e, 0x50, 0x7b, 0xb5, 0x55, 0xd8, 0xba, 0xb1, 0xf3, 0xbd, 0x3c, 0x4f, 0xea, 0xe3, 0x98, 0x07, + 0x14, 0x5b, 0x86, 0xa1, 0x26, 0x68, 0x0f, 0x60, 0x42, 0x27, 0xa7, 0x54, 0x44, 0x63, 0x3f, 0xb4, + 0xd7, 0x34, 0xfd, 0x07, 0xd7, 0xd1, 0x55, 0xec, 0x9d, 0xc3, 0x39, 0x1c, 0x67, 0xa8, 0xe8, 0x10, + 0x6a, 0x64, 0x4a, 0xfc, 0x80, 0x9c, 0xfa, 0x81, 0x2f, 0x67, 0x76, 0x51, 0xbb, 0xfa, 0xe4, 0xad, + 0xae, 0xba, 0x19, 0x02, 0x5e, 0xa2, 0xb7, 0x5d, 0x80, 0xc5, 0x87, 0xd0, 0x23, 0x28, 0x0f, 0x07, + 0x47, 0xfd, 0xfd, 0xa3, 0xbd, 0xfa, 0x4a, 0xe3, 0xde, 0xcb, 0x8b, 0xd6, 0x1d, 0xe5, 0x63, 0x01, + 0x18, 0x52, 0xe6, 0xfa, 0xcc, 0x43, 0x5b, 0x50, 0xe9, 0xee, 0xee, 0x0e, 0x86, 0x27, 0x83, 0x7e, + 0xbd, 0xd0, 0x68, 0xbc, 0xbc, 0x68, 0xdd, 0x5d, 0x06, 0x76, 0x1d, 0x87, 0x86, 0x92, 0xba, 0x8d, + 0xe2, 0x37, 0x7f, 0x69, 0xae, 0xb4, 0xbf, 0x29, 0x40, 0x2d, 0x1b, 0x04, 0x7a, 0x04, 0xa5, 0xee, + 0xee, 0xc9, 0xfe, 0xf3, 0x41, 0x7d, 0x65, 0x41, 0xcf, 0x22, 0xba, 0x8e, 0xf4, 0xa7, 0x14, 0x3d, + 0x84, 0xf5, 0x61, 0xf7, 0xab, 0xe3, 0x41, 0xbd, 0xb0, 0x08, 0x27, 0x0b, 0x1b, 0x92, 0x38, 0xd2, + 0xa8, 0x3e, 0xee, 0xee, 0x1f, 0xd5, 0x57, 0xf3, 0x51, 0x7d, 0x41, 0x7c, 0x66, 0x42, 0xf9, 0x73, + 0x11, 0xac, 0x63, 0x2a, 0xa6, 0xbe, 0xf3, 0x81, 0x25, 0xf2, 0x19, 0x14, 0x25, 0x89, 0xce, 0xb5, + 0x34, 0xac, 0x7c, 0x69, 0x9c, 0x90, 0xe8, 0x5c, 0x7d, 0xd4, 0xd0, 0x35, 0x5e, 0x29, 0x43, 0xd0, + 0x30, 0xf0, 0x1d, 0x22, 0xa9, 0xab, 0x95, 0x61, 0xed, 0x7c, 0x3f, 0x8f, 0x8d, 0xe7, 0x28, 0x13, + 0xff, 0xd3, 0x15, 0x9c, 0xa1, 0xa2, 0x27, 0x50, 0xf2, 0x02, 0x7e, 0x4a, 0x02, 0xad, 0x09, 0x6b, + 0xe7, 0x41, 0x9e, 0x93, 0x3d, 0x8d, 0x58, 0x38, 0x30, 0x14, 0xf4, 0x18, 0x4a, 0x71, 0xe8, 0x12, + 0x49, 0xed, 0x92, 0x26, 0xb7, 0xf2, 0xc8, 0x5f, 0x69, 0xc4, 0x2e, 0x67, 0x67, 0xbe, 0x87, 0x0d, + 0x1e, 0x1d, 0x40, 0x85, 0x51, 0xf9, 0x35, 0x17, 0xe7, 0x91, 0x5d, 0x6e, 0xad, 0x6d, 0x59, 0x3b, + 0x9f, 0xe6, 0x8a, 0x31, 0xc1, 0x74, 0xa5, 0x24, 0xce, 0x78, 0x42, 0x99, 0x4c, 0xdc, 0xf4, 0x56, + 0xed, 0x02, 0x9e, 0x3b, 0x40, 0x3f, 0x87, 0x0a, 0x65, 0x6e, 0xc8, 0x7d, 0x26, 0xed, 0xca, 0xf5, + 0x81, 0x0c, 0x0c, 0x46, 0x25, 0x13, 0xcf, 0x19, 0x8a, 0x2d, 0x78, 0x10, 0x9c, 0x12, 0xe7, 0xdc, + 0xae, 0xbe, 0xe7, 0x36, 0xe6, 0x8c, 0x5e, 0x09, 0x8a, 0x13, 0xee, 0xd2, 0xf6, 0x36, 0xdc, 0x7a, + 0x23, 0xd5, 0xa8, 0x01, 0x15, 0x93, 0xea, 0x44, 0x23, 0x45, 0x3c, 0x9f, 0xb7, 0x6f, 0xc2, 0xc6, + 0x52, 0x5a, 0xdb, 0x7f, 0x2f, 0x42, 0x25, 0x3d, 0x6b, 0xd4, 0x85, 0xaa, 0xc3, 0x99, 0x24, 0x3e, + 0xa3, 0xc2, 0xc8, 0x2b, 0xf7, 0x64, 0x76, 0x53, 0x90, 0x62, 0x3d, 0x5d, 0xc1, 0x0b, 0x16, 0xfa, + 0x35, 0x54, 0x05, 0x8d, 0x78, 0x2c, 0x1c, 0x1a, 0x19, 0x7d, 0x6d, 0xe5, 0x2b, 0x24, 0x01, 0x61, + 0xfa, 0x87, 0xd8, 0x17, 0x54, 0x65, 0x39, 0xc2, 0x0b, 0x2a, 0x7a, 0x02, 0x65, 0x41, 0x23, 0x49, + 0x84, 0x7c, 0x9b, 0x44, 0x70, 0x02, 0x19, 0xf2, 0xc0, 0x77, 0x66, 0x38, 0x65, 0xa0, 0x27, 0x50, + 0x0d, 0x03, 0xe2, 0x68, 0xaf, 0xf6, 0xba, 0xa6, 0x7f, 0x94, 0x47, 0x1f, 0xa6, 0x20, 0xbc, 0xc0, + 0xa3, 0xcf, 0x01, 0x02, 0xee, 0x8d, 0x5c, 0xe1, 0x4f, 0xa9, 0x30, 0x12, 0x6b, 0xe4, 0xb1, 0xfb, + 0x1a, 0x81, 0xab, 0x01, 0xf7, 0x92, 0x21, 0xda, 0xfb, 0x9f, 0xf4, 0x95, 0xd1, 0xd6, 0x01, 0x00, + 0x99, 0xaf, 0x1a, 0x75, 0x7d, 0xf2, 0x5e, 0xae, 0xcc, 0x89, 0x64, 0xe8, 0xe8, 0x01, 0xd4, 0xce, + 0xb8, 0x70, 0xe8, 0xc8, 0xdc, 0x9a, 0xaa, 0xd6, 0x84, 0xa5, 0x6d, 0x89, 0xbe, 0x50, 0x0f, 0xca, + 0x1e, 0x65, 0x54, 0xf8, 0x8e, 0x0d, 0xfa, 0x63, 0x8f, 0x72, 0x2f, 0x64, 0x02, 0xc1, 0x31, 0x93, + 0xfe, 0x84, 0x9a, 0x2f, 0xa5, 0xc4, 0x5e, 0x15, 0xca, 0x22, 0x59, 0x69, 0xff, 0x1e, 0xd0, 0x9b, + 0x58, 0x84, 0xa0, 0x78, 0xee, 0x33, 0x57, 0x0b, 0xab, 0x8a, 0xf5, 0x18, 0x75, 0xa0, 0x1c, 0x92, + 0x59, 0xc0, 0x89, 0x6b, 0xc4, 0x72, 0xbb, 0x93, 0xd4, 0xcb, 0x4e, 0x5a, 0x2f, 0x3b, 0x5d, 0x36, + 0xc3, 0x29, 0xa8, 0x7d, 0x00, 0x77, 0x72, 0xb7, 0x8c, 0x76, 0xa0, 0x36, 0x17, 0xe1, 0xc8, 0x37, + 0x1f, 0xe9, 0xdd, 0xbc, 0xba, 0xdc, 0xb4, 0xe6, 0x6a, 0xdd, 0xef, 0x63, 0x6b, 0x0e, 0xda, 0x77, + 0xdb, 0x7f, 0xaa, 0xc2, 0xc6, 0x92, 0x94, 0xd1, 0x6d, 0x58, 0xf7, 0x27, 0xc4, 0xa3, 0x26, 0xc6, + 0x64, 0x82, 0x06, 0x50, 0x0a, 0xc8, 0x29, 0x0d, 0x94, 0xa0, 0xd5, 0xa1, 0xfe, 0xe8, 0x9d, 0x77, + 0xa2, 0xf3, 0x5b, 0x8d, 0x1f, 0x30, 0x29, 0x66, 0xd8, 0x90, 0x91, 0x0d, 0x65, 0x87, 0x4f, 0x26, + 0x84, 0xa9, 0xa7, 0x73, 0x6d, 0xab, 0x8a, 0xd3, 0xa9, 0xca, 0x0c, 0x11, 0x5e, 0x64, 0x17, 0xb5, + 0x59, 0x8f, 0x51, 0x1d, 0xd6, 0x28, 0x9b, 0xda, 0xeb, 0xda, 0xa4, 0x86, 0xca, 0xe2, 0xfa, 0x89, + 0x22, 0xab, 0x58, 0x0d, 0x15, 0x2f, 0x8e, 0xa8, 0xb0, 0xcb, 0x49, 0x46, 0xd5, 0x18, 0xfd, 0x0c, + 0x4a, 0x13, 0x1e, 0x33, 0x19, 0xd9, 0x15, 0x1d, 0xec, 0xbd, 0xbc, 0x60, 0x0f, 0x15, 0xc2, 0x3c, + 0xed, 0x06, 0x8e, 0x06, 0x70, 0x2b, 0x92, 0x3c, 0x1c, 0x79, 0x82, 0x38, 0x74, 0x14, 0x52, 0xe1, + 0x73, 0xd7, 0x3c, 0x4d, 0xf7, 0xde, 0x38, 0x94, 0xbe, 0x69, 0x72, 0xf0, 0x4d, 0xc5, 0xd9, 0x53, + 0x94, 0xa1, 0x66, 0xa0, 0x21, 0xd4, 0xc2, 0x38, 0x08, 0x46, 0x3c, 0x4c, 0xaa, 0x54, 0xa2, 0xa7, + 0xf7, 0x48, 0xd9, 0x30, 0x0e, 0x82, 0x67, 0x09, 0x09, 0x5b, 0xe1, 0x62, 0x82, 0xee, 0x42, 0xc9, + 0x13, 0x3c, 0x0e, 0x23, 0xdb, 0xd2, 0xc9, 0x30, 0x33, 0xf4, 0x25, 0x94, 0x23, 0xea, 0x08, 0x2a, + 0x23, 0xbb, 0xa6, 0xb7, 0xfa, 0x71, 0xde, 0x47, 0x8e, 0x35, 0x04, 0xd3, 0x33, 0x2a, 0x28, 0x73, + 0x28, 0x4e, 0x39, 0xe8, 0x1e, 0xac, 0x49, 0x39, 0xb3, 0x37, 0x5a, 0x85, 0xad, 0x4a, 0xaf, 0x7c, + 0x75, 0xb9, 0xb9, 0x76, 0x72, 0xf2, 0x02, 0x2b, 0x9b, 0x7a, 0x41, 0xc7, 0x3c, 0x92, 0x8c, 0x4c, + 0xa8, 0x7d, 0x43, 0xe7, 0x76, 0x3e, 0x47, 0x2f, 0x00, 0x5c, 0x16, 0x8d, 0x1c, 0x7d, 0x65, 0xed, + 0x9b, 0x7a, 0x77, 0x9f, 0xbe, 0x7b, 0x77, 0xfd, 0xa3, 0x63, 0x53, 0x45, 0x36, 0xae, 0x2e, 0x37, + 0xab, 0xf3, 0x29, 0xae, 0xba, 0x2c, 0x4a, 0x86, 0xa8, 0x07, 0xd6, 0x98, 0x92, 0x40, 0x8e, 0x9d, + 0x31, 0x75, 0xce, 0xed, 0xfa, 0xf5, 0x65, 0xe1, 0xa9, 0x86, 0x19, 0x0f, 0x59, 0x92, 0x52, 0xb0, + 0x0a, 0x35, 0xb2, 0x6f, 0xe9, 0x5c, 0x25, 0x13, 0xf4, 0x11, 0x00, 0x0f, 0x29, 0x1b, 0x45, 0xd2, + 0xf5, 0x99, 0x8d, 0xd4, 0x96, 0x71, 0x55, 0x59, 0x8e, 0x95, 0x01, 0xdd, 0x57, 0x8f, 0x36, 0x71, + 0x47, 0x9c, 0x05, 0x33, 0xfb, 0x3b, 0x7a, 0xb5, 0xa2, 0x0c, 0xcf, 0x58, 0x30, 0x43, 0x9b, 0x60, + 0x69, 0x5d, 0x44, 0xbe, 0xc7, 0x48, 0x60, 0xdf, 0xd6, 0xf9, 0x00, 0x65, 0x3a, 0xd6, 0x16, 0x75, + 0x0e, 0x49, 0x36, 0x22, 0xfb, 0xce, 0xf5, 0xe7, 0x60, 0x82, 0x5d, 0x9c, 0x83, 0xe1, 0xa0, 0x5f, + 0x00, 0x84, 0xc2, 0x9f, 0xfa, 0x01, 0xf5, 0x68, 0x64, 0xdf, 0xd5, 0x9b, 0x6e, 0xe6, 0xbe, 0xd6, + 0x73, 0x14, 0xce, 0x30, 0x1a, 0x9f, 0x83, 0x95, 0xb9, 0x6d, 0xea, 0x96, 0x9c, 0xd3, 0x99, 0xb9, + 0xc0, 0x6a, 0xa8, 0x52, 0x32, 0x25, 0x41, 0x9c, 0x74, 0xc2, 0x55, 0x9c, 0x4c, 0xbe, 0x58, 0x7d, + 0x5c, 0x68, 0xec, 0x80, 0x95, 0x51, 0x1d, 0xfa, 0x18, 0x36, 0x04, 0xf5, 0xfc, 0x48, 0x8a, 0xd9, + 0x88, 0xc4, 0x72, 0x6c, 0xff, 0x4a, 0x13, 0x6a, 0xa9, 0xb1, 0x1b, 0xcb, 0x71, 0x63, 0x04, 0x8b, + 0xc3, 0x43, 0x2d, 0xb0, 0x94, 0x28, 0x22, 0x2a, 0xa6, 0x54, 0xa8, 0x6a, 0xab, 0x72, 0x9e, 0x35, + 0x29, 0xf1, 0x46, 0x94, 0x08, 0x67, 0xac, 0xdf, 0x8e, 0x2a, 0x36, 0x33, 0xf5, 0x18, 0xa4, 0x37, + 0xc4, 0x3c, 0x06, 0x66, 0xda, 0xfe, 0x57, 0x01, 0x6a, 0xd9, 0xa6, 0x01, 0xed, 0x26, 0xc5, 0x5e, + 0x6f, 0xe9, 0xc6, 0xce, 0xf6, 0xbb, 0x9a, 0x0c, 0x5d, 0x5a, 0x83, 0x58, 0x39, 0x3b, 0x54, 0xfd, + 0xbd, 0x26, 0xa3, 0x9f, 0xc2, 0x7a, 0xc8, 0x85, 0x4c, 0x9f, 0xb0, 0xfc, 0x04, 0x73, 0x91, 0x96, + 0xa2, 0x04, 0xdc, 0x1e, 0xc3, 0x8d, 0x65, 0x6f, 0xe8, 0x21, 0xac, 0x3d, 0xdf, 0x1f, 0xd6, 0x57, + 0x1a, 0xf7, 0x5f, 0x5e, 0xb4, 0xbe, 0xbb, 0xbc, 0xf8, 0xdc, 0x17, 0x32, 0x26, 0xc1, 0xfe, 0x10, + 0xfd, 0x10, 0xd6, 0xfb, 0x47, 0xc7, 0x18, 0xd7, 0x0b, 0x8d, 0xcd, 0x97, 0x17, 0xad, 0xfb, 0xcb, + 0x38, 0xb5, 0xc4, 0x63, 0xe6, 0x62, 0x7e, 0x3a, 0xef, 0x75, 0xff, 0xbd, 0x0a, 0x96, 0x79, 0xd9, + 0x3f, 0xf4, 0xef, 0xd0, 0x46, 0x52, 0xca, 0xd3, 0x2b, 0xbb, 0xfa, 0xce, 0x8a, 0x5e, 0x4b, 0x08, + 0xe6, 0x8c, 0x1f, 0x40, 0xcd, 0x0f, 0xa7, 0x9f, 0x8d, 0x28, 0x23, 0xa7, 0x81, 0x69, 0x7b, 0x2b, + 0xd8, 0x52, 0xb6, 0x41, 0x62, 0x52, 0xef, 0x85, 0xcf, 0x24, 0x15, 0xcc, 0x34, 0xb4, 0x15, 0x3c, + 0x9f, 0xa3, 0x2f, 0xa1, 0xe8, 0x87, 0x64, 0x62, 0xda, 0x90, 0xdc, 0x1d, 0xec, 0x0f, 0xbb, 0x87, + 0x46, 0x83, 0xbd, 0xca, 0xd5, 0xe5, 0x66, 0x51, 0x19, 0xb0, 0xa6, 0xa1, 0x66, 0xda, 0x09, 0xa8, + 0x2f, 0xe9, 0xb7, 0xbf, 0x82, 0x33, 0x16, 0xa5, 0x23, 0x9f, 0x79, 0x82, 0x46, 0x91, 0xae, 0x02, + 0x15, 0x9c, 0x4e, 0x51, 0x03, 0xca, 0xa6, 0x9f, 0xd0, 0x0d, 0x44, 0x55, 0xd5, 0x6a, 0x63, 0xe8, + 0x6d, 0x80, 0x95, 0x64, 0x63, 0x74, 0x26, 0xf8, 0xa4, 0xfd, 0x9f, 0x22, 0x58, 0xbb, 0x41, 0x1c, + 0x49, 0x53, 0x06, 0x3f, 0x58, 0xf2, 0x5f, 0xc0, 0x2d, 0xa2, 0x7f, 0xaf, 0x08, 0x53, 0x35, 0x45, + 0xb7, 0x69, 0xe6, 0x00, 0x1e, 0xe6, 0xba, 0x9b, 0x83, 0x93, 0x96, 0xae, 0x57, 0x52, 0x3e, 0xed, + 0x02, 0xae, 0x93, 0xd7, 0x56, 0xd0, 0x31, 0x6c, 0x70, 0xe1, 0x8c, 0x69, 0x24, 0x93, 0x4a, 0x64, + 0x7e, 0x47, 0x72, 0x7f, 0x54, 0x9f, 0x65, 0x81, 0xe6, 0x19, 0x4e, 0xa2, 0x5d, 0xf6, 0x81, 0x1e, + 0x43, 0x51, 0x90, 0xb3, 0xb4, 0xe5, 0xcc, 0xbd, 0x24, 0x98, 0x9c, 0xc9, 0x25, 0x17, 0x9a, 0x81, + 0x7e, 0x03, 0xe0, 0xfa, 0x51, 0x48, 0xa4, 0x33, 0xa6, 0xc2, 0x1c, 0x76, 0xee, 0x16, 0xfb, 0x73, + 0xd4, 0x92, 0x97, 0x0c, 0x1b, 0x1d, 0x40, 0xd5, 0x21, 0xa9, 0x5c, 0x4b, 0xd7, 0xff, 0xa3, 0xed, + 0x76, 0x8d, 0x8b, 0xba, 0x72, 0x71, 0x75, 0xb9, 0x59, 0x49, 0x2d, 0xb8, 0xe2, 0x10, 0x23, 0xdf, + 0x03, 0xd8, 0x50, 0xff, 0x6e, 0x23, 0x97, 0x9e, 0x91, 0x38, 0x90, 0x89, 0x4c, 0xae, 0x29, 0x2b, + 0xea, 0x47, 0xa0, 0x6f, 0x70, 0x26, 0xae, 0x9a, 0xcc, 0xd8, 0xd0, 0xef, 0xe0, 0x16, 0x65, 0x8e, + 0x98, 0x69, 0xb1, 0xa6, 0x11, 0x56, 0xae, 0xdf, 0xec, 0x60, 0x0e, 0x5e, 0xda, 0x6c, 0x9d, 0xbe, + 0x66, 0x6f, 0xff, 0xb5, 0x00, 0x90, 0x54, 0xea, 0x0f, 0x2b, 0x40, 0x04, 0x45, 0x97, 0x48, 0xa2, + 0x35, 0x57, 0xc3, 0x7a, 0x8c, 0xbe, 0x00, 0x90, 0x74, 0x12, 0x06, 0x44, 0xfa, 0xcc, 0x33, 0xb2, + 0x79, 0xdb, 0x73, 0x90, 0x41, 0xeb, 0x38, 0x93, 0x90, 0xff, 0xaf, 0xe3, 0xec, 0xd9, 0xaf, 0xbe, + 0x6d, 0xae, 0xfc, 0xe3, 0xdb, 0xe6, 0xca, 0x1f, 0xaf, 0x9a, 0x85, 0x57, 0x57, 0xcd, 0xc2, 0xdf, + 0xae, 0x9a, 0x85, 0x7f, 0x5e, 0x35, 0x0b, 0xa7, 0x25, 0xdd, 0xc3, 0xfd, 0xe4, 0xbf, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x06, 0x93, 0x6e, 0xba, 0xfc, 0x12, 0x00, 0x00, } diff --git a/api/specs.proto b/api/specs.proto index f7a4d18b2e..13b52d4105 100644 --- a/api/specs.proto +++ b/api/specs.proto @@ -386,6 +386,13 @@ message SecretSpec { // Data is the secret payload - the maximum size is 500KB (that is, 500*1024 bytes) bytes data = 2; + + // Templating controls whether and how to evaluate the secret payload as + // a template. If it is not set, no templating is used. + // + // The currently recognized values are: + // - golang: Go templating + Driver templating = 3; } // ConfigSpec specifies user-provided configuration files. @@ -396,4 +403,11 @@ message ConfigSpec { // TODO(aaronl): Do we want to revise this to include multiple payloads in a single // ConfigSpec? Define this to be a tar? etc... bytes data = 2; + + // Templating controls whether and how to evaluate the secret payload as + // a template. If it is not set, no templating is used. + // + // The currently recognized values are: + // - golang: Go templating + Driver templating = 3; } diff --git a/ca/config_test.go b/ca/config_test.go index f16a5c5735..5c28f70dac 100644 --- a/ca/config_test.go +++ b/ca/config_test.go @@ -1,6 +1,7 @@ package ca_test import ( + "bytes" "crypto/tls" "crypto/x509" "fmt" @@ -21,10 +22,11 @@ import ( "github.com/cloudflare/cfssl/helpers" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" - "github.com/docker/swarmkit/ca/testutils" + cautils "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/log" "github.com/docker/swarmkit/manager/state" "github.com/docker/swarmkit/manager/state/store" + "github.com/docker/swarmkit/testutils" "github.com/docker/swarmkit/watch" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -32,7 +34,7 @@ import ( ) func TestDownloadRootCASuccess(t *testing.T) { - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() // Remove the CA cert @@ -60,7 +62,7 @@ func TestDownloadRootCASuccess(t *testing.T) { } func TestDownloadRootCAWrongCAHash(t *testing.T) { - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() // Remove the CA cert @@ -89,10 +91,10 @@ func TestDownloadRootCAWrongCAHash(t *testing.T) { } func TestCreateSecurityConfigEmptyDir(t *testing.T) { - if testutils.External { + if cautils.External { return // this doesn't require any servers at all } - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() assert.NoError(t, tc.CAServer.Stop()) @@ -120,7 +122,7 @@ func TestCreateSecurityConfigEmptyDir(t *testing.T) { } func TestCreateSecurityConfigNoCerts(t *testing.T) { - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) @@ -162,10 +164,10 @@ func TestCreateSecurityConfigNoCerts(t *testing.T) { } func TestLoadSecurityConfigExpiredCert(t *testing.T) { - if testutils.External { + if cautils.External { return // this doesn't require any servers at all } - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() s, err := tc.RootCA.Signer() require.NoError(t, err) @@ -179,7 +181,7 @@ func TestLoadSecurityConfigExpiredCert(t *testing.T) { require.NoError(t, err) // A cert that is not yet valid is not valid even if expiry is allowed - invalidCert := testutils.ReDateCert(t, certBytes, tc.RootCA.Certs, s.Key, now.Add(time.Hour), now.Add(time.Hour*2)) + invalidCert := cautils.ReDateCert(t, certBytes, tc.RootCA.Certs, s.Key, now.Add(time.Hour), now.Add(time.Hour*2)) require.NoError(t, ioutil.WriteFile(tc.Paths.Node.Cert, invalidCert, 0700)) _, err = ca.LoadSecurityConfig(tc.Context, tc.RootCA, krw, false) @@ -191,7 +193,7 @@ func TestLoadSecurityConfigExpiredCert(t *testing.T) { require.IsType(t, x509.CertificateInvalidError{}, errors.Cause(err)) // a cert that is expired is not valid if expiry is not allowed - invalidCert = testutils.ReDateCert(t, certBytes, tc.RootCA.Certs, s.Key, now.Add(-2*time.Minute), now.Add(-1*time.Minute)) + invalidCert = cautils.ReDateCert(t, certBytes, tc.RootCA.Certs, s.Key, now.Add(-2*time.Minute), now.Add(-1*time.Minute)) require.NoError(t, ioutil.WriteFile(tc.Paths.Node.Cert, invalidCert, 0700)) _, err = ca.LoadSecurityConfig(tc.Context, tc.RootCA, krw, false) @@ -204,10 +206,10 @@ func TestLoadSecurityConfigExpiredCert(t *testing.T) { } func TestLoadSecurityConfigInvalidCert(t *testing.T) { - if testutils.External { + if cautils.External { return // this doesn't require any servers at all } - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() // Write some garbage to the cert @@ -222,10 +224,10 @@ some random garbage\n } func TestLoadSecurityConfigInvalidKey(t *testing.T) { - if testutils.External { + if cautils.External { return // this doesn't require any servers at all } - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() // Write some garbage to the Key @@ -240,10 +242,10 @@ some random garbage\n } func TestLoadSecurityConfigIncorrectPassphrase(t *testing.T) { - if testutils.External { + if cautils.External { return // this doesn't require any servers at all } - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() paths := ca.NewConfigPaths(tc.TempDir) @@ -256,7 +258,7 @@ func TestLoadSecurityConfigIncorrectPassphrase(t *testing.T) { } func TestLoadSecurityConfigIntermediates(t *testing.T) { - if testutils.External { + if cautils.External { return // this doesn't require any servers at all } tempdir, err := ioutil.TempDir("", "test-load-config-with-intermediates") @@ -265,7 +267,7 @@ func TestLoadSecurityConfigIntermediates(t *testing.T) { paths := ca.NewConfigPaths(tempdir) krw := ca.NewKeyReadWriter(paths.Node, nil, nil) - rootCA, err := ca.NewRootCA(testutils.ECDSACertChain[2], nil, nil, ca.DefaultNodeCertExpiration, nil) + rootCA, err := ca.NewRootCA(cautils.ECDSACertChain[2], nil, nil, ca.DefaultNodeCertExpiration, nil) require.NoError(t, err) ctx := log.WithLogger(context.Background(), log.L.WithFields(logrus.Fields{ @@ -274,15 +276,15 @@ func TestLoadSecurityConfigIntermediates(t *testing.T) { })) // loading the incomplete chain fails - require.NoError(t, krw.Write(testutils.ECDSACertChain[0], testutils.ECDSACertChainKeys[0], nil)) + require.NoError(t, krw.Write(cautils.ECDSACertChain[0], cautils.ECDSACertChainKeys[0], nil)) _, err = ca.LoadSecurityConfig(ctx, rootCA, krw, false) require.Error(t, err) - intermediate, err := helpers.ParseCertificatePEM(testutils.ECDSACertChain[1]) + intermediate, err := helpers.ParseCertificatePEM(cautils.ECDSACertChain[1]) require.NoError(t, err) // loading the complete chain succeeds - require.NoError(t, krw.Write(append(testutils.ECDSACertChain[0], testutils.ECDSACertChain[1]...), testutils.ECDSACertChainKeys[0], nil)) + require.NoError(t, krw.Write(append(cautils.ECDSACertChain[0], cautils.ECDSACertChain[1]...), cautils.ECDSACertChainKeys[0], nil)) secConfig, err := ca.LoadSecurityConfig(ctx, rootCA, krw, false) require.NoError(t, err) require.NotNil(t, secConfig) @@ -314,13 +316,13 @@ func TestLoadSecurityConfigIntermediates(t *testing.T) { // When the root CA is updated on the security config, the root pools are updated // and the external CA's rootCA is also updated. func TestSecurityConfigUpdateRootCA(t *testing.T) { - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() tcConfig, err := tc.NewNodeConfig("worker") require.NoError(t, err) // create the "original" security config, and we'll update it to trust the test server's - cert, key, err := testutils.CreateRootCertAndKey("root1") + cert, key, err := cautils.CreateRootCertAndKey("root1") require.NoError(t, err) rootCA, err := ca.NewRootCA(cert, cert, key, ca.DefaultNodeCertExpiration, nil) @@ -370,11 +372,11 @@ func TestSecurityConfigUpdateRootCA(t *testing.T) { externalServer := tc.ExternalSigningServer tcSigner, err := tc.RootCA.Signer() require.NoError(t, err) - if testutils.External { + if cautils.External { // stop the external server and create a new one because the external server actually has to trust our client certs as well. updatedRoot, err := ca.NewRootCA(append(tc.RootCA.Certs, cert...), tcSigner.Cert, tcSigner.Key, ca.DefaultNodeCertExpiration, nil) require.NoError(t, err) - externalServer, err = testutils.NewExternalSigningServer(updatedRoot, tc.TempDir) + externalServer, err = cautils.NewExternalSigningServer(updatedRoot, tc.TempDir) require.NoError(t, err) defer externalServer.Stop() @@ -414,7 +416,7 @@ func TestSecurityConfigUpdateRootCA(t *testing.T) { // make sure any generated certs after updating contain the intermediate var generatedCert []byte - if testutils.External { + if cautils.External { // we can also now connect to the test CA's external signing server secConfig.ExternalCA().UpdateURLs(externalServer.URL) generatedCert, err = secConfig.ExternalCA().Sign(tc.Context, req) @@ -438,7 +440,7 @@ func TestSecurityConfigUpdateRootCA(t *testing.T) { // You can't update the root CA to one that doesn't match the TLS certificates func TestSecurityConfigUpdateRootCAUpdateConsistentWithTLSCertificates(t *testing.T) { t.Parallel() - if testutils.External { + if cautils.External { return // we don't care about external CAs at all } tempdir, err := ioutil.TempDir("", "") @@ -485,7 +487,7 @@ func TestSecurityConfigUpdateRootCAUpdateConsistentWithTLSCertificates(t *testin } func TestSecurityConfigSetWatch(t *testing.T) { - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() secConfig, err := tc.NewNodeConfig(ca.ManagerRole) @@ -551,7 +553,7 @@ func TestRenewTLSConfigUpdatesRootOnUnknownAuthError(t *testing.T) { cas = make([]ca.RootCA, 3) ) for i := 0; i < 3; i++ { - certs[i], keys[i], err = testutils.CreateRootCertAndKey(fmt.Sprintf("CA%d", i)) + certs[i], keys[i], err = cautils.CreateRootCertAndKey(fmt.Sprintf("CA%d", i)) require.NoError(t, err) switch i { case 0: @@ -568,7 +570,7 @@ func TestRenewTLSConfigUpdatesRootOnUnknownAuthError(t *testing.T) { // the CA server is going to start off with a cert issued by the second CA, cross-signed by the first CA, and then // rotate to one issued by the third CA, cross-signed by the second. - tc := testutils.NewTestCAFromAPIRootCA(t, tempdir, api.RootCA{ + tc := cautils.NewTestCAFromAPIRootCA(t, tempdir, api.RootCA{ CACert: certs[0], CAKey: keys[0], RootRotation: &api.RootRotation{ @@ -589,6 +591,24 @@ func TestRenewTLSConfigUpdatesRootOnUnknownAuthError(t *testing.T) { } return store.UpdateCluster(tx, cluster) })) + // wait until the CA is returning certs signed by the latest root + rootCA, err := ca.NewRootCA(certs[1], nil, nil, ca.DefaultNodeCertExpiration, nil) + require.NoError(t, err) + expectedIssuer, err := helpers.ParseCertificatePEM(certs[2]) + require.NoError(t, err) + require.NoError(t, testutils.PollFuncWithTimeout(nil, func() error { + _, issuerInfo, err := rootCA.RequestAndSaveNewCertificates(tc.Context, tc.KeyReadWriter, ca.CertificateRequestConfig{ + Token: tc.WorkerToken, + ConnBroker: tc.ConnBroker, + }) + if err != nil { + return err + } + if !bytes.Equal(issuerInfo.PublicKey, expectedIssuer.RawSubjectPublicKeyInfo) { + return errors.New("CA server hasn't finished updating yet") + } + return nil + }, 2*time.Second)) paths := ca.NewConfigPaths(tempdir) krw := ca.NewKeyReadWriter(paths.Node, nil, nil) @@ -657,12 +677,12 @@ func TestRenewTLSConfigUpdatesRootNonUnknownAuthError(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(tempdir) - cert, key, err := testutils.CreateRootCertAndKey("rootCA") + cert, key, err := cautils.CreateRootCertAndKey("rootCA") require.NoError(t, err) rootCA, err := ca.NewRootCA(cert, cert, key, ca.DefaultNodeCertExpiration, nil) require.NoError(t, err) - tc := testutils.NewTestCAFromAPIRootCA(t, tempdir, api.RootCA{ + tc := cautils.NewTestCAFromAPIRootCA(t, tempdir, api.RootCA{ CACert: cert, CAKey: key, }, nil) @@ -689,7 +709,7 @@ func TestRenewTLSConfigUpdatesRootNonUnknownAuthError(t *testing.T) { if err != nil { return err } - node.Certificate.Certificate = testutils.ReDateCert(t, certChain, cert, key, time.Now().Add(-5*time.Hour), time.Now().Add(-4*time.Hour)) + node.Certificate.Certificate = cautils.ReDateCert(t, certChain, cert, key, time.Now().Add(-5*time.Hour), time.Now().Add(-4*time.Hour)) node.Certificate.Status = api.IssuanceStatus{ State: api.IssuanceStateIssued, } @@ -709,7 +729,7 @@ func TestRenewTLSConfigUpdatesRootNonUnknownAuthError(t *testing.T) { // enforce that no matter what order updating the root CA and updating TLS credential happens, we // end up with a security config that has updated certs, and an updated root pool func TestRenewTLSConfigUpdateRootCARace(t *testing.T) { - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() paths := ca.NewConfigPaths(tc.TempDir) @@ -719,11 +739,11 @@ func TestRenewTLSConfigUpdateRootCARace(t *testing.T) { leafCert, err := ioutil.ReadFile(paths.Node.Cert) require.NoError(t, err) - cert, key, err := testutils.CreateRootCertAndKey("extra root cert for external CA") + cert, key, err := cautils.CreateRootCertAndKey("extra root cert for external CA") require.NoError(t, err) extraExternalRootCA, err := ca.NewRootCA(append(cert, tc.RootCA.Certs...), cert, key, ca.DefaultNodeCertExpiration, nil) require.NoError(t, err) - extraExternalServer, err := testutils.NewExternalSigningServer(extraExternalRootCA, tc.TempDir) + extraExternalServer, err := cautils.NewExternalSigningServer(extraExternalRootCA, tc.TempDir) require.NoError(t, err) defer extraExternalServer.Stop() secConfig.ExternalCA().UpdateURLs(extraExternalServer.URL) @@ -737,7 +757,7 @@ func TestRenewTLSConfigUpdateRootCARace(t *testing.T) { signReq := ca.PrepareCSR(csr, "cn", ca.WorkerRole, tc.Organization) for i := 0; i < 5; i++ { - cert, _, err := testutils.CreateRootCertAndKey(fmt.Sprintf("root %d", i+2)) + cert, _, err := cautils.CreateRootCertAndKey(fmt.Sprintf("root %d", i+2)) require.NoError(t, err) ctx, cancel := context.WithCancel(tc.Context) @@ -780,7 +800,7 @@ func TestRenewTLSConfigUpdateRootCARace(t *testing.T) { } } -func writeAlmostExpiringCertToDisk(t *testing.T, tc *testutils.TestCA, cn, ou, org string) { +func writeAlmostExpiringCertToDisk(t *testing.T, tc *cautils.TestCA, cn, ou, org string) { s, err := tc.RootCA.Signer() require.NoError(t, err) @@ -807,7 +827,7 @@ func writeAlmostExpiringCertToDisk(t *testing.T, tc *testutils.TestCA, cn, ou, o func TestRenewTLSConfigWorker(t *testing.T) { t.Parallel() - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() ctx, cancel := context.WithCancel(tc.Context) @@ -843,7 +863,7 @@ func TestRenewTLSConfigWorker(t *testing.T) { func TestRenewTLSConfigManager(t *testing.T) { t.Parallel() - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() ctx, cancel := context.WithCancel(tc.Context) @@ -879,7 +899,7 @@ func TestRenewTLSConfigManager(t *testing.T) { func TestRenewTLSConfigWithNoNode(t *testing.T) { t.Parallel() - tc := testutils.NewTestCA(t) + tc := cautils.NewTestCA(t) defer tc.Stop() ctx, cancel := context.WithCancel(tc.Context) diff --git a/manager/allocator/cnmallocator/drivers_network_windows.go b/manager/allocator/cnmallocator/drivers_network_windows.go index 8cbedbd6b8..e383b60705 100644 --- a/manager/allocator/cnmallocator/drivers_network_windows.go +++ b/manager/allocator/cnmallocator/drivers_network_windows.go @@ -3,12 +3,17 @@ package cnmallocator import ( "github.com/docker/libnetwork/drivers/overlay/ovmanager" "github.com/docker/libnetwork/drivers/remote" + "github.com/docker/libnetwork/drivers/windows" "github.com/docker/swarmkit/manager/allocator/networkallocator" ) var initializers = []initializer{ {remote.Init, "remote"}, {ovmanager.Init, "overlay"}, + {windows.GetInit("transparent"), "transparent"}, + {windows.GetInit("l2bridge"), "l2bridge"}, + {windows.GetInit("l2tunnel"), "l2tunnel"}, + {windows.GetInit("nat"), "nat"}, } // PredefinedNetworks returns the list of predefined network structures diff --git a/manager/controlapi/cluster.go b/manager/controlapi/cluster.go index 9832272a3f..7e9dea2ce5 100644 --- a/manager/controlapi/cluster.go +++ b/manager/controlapi/cluster.go @@ -247,6 +247,11 @@ func redactClusters(clusters []*api.Cluster) []*api.Cluster { // Do not copy secret keys redactedSpec := cluster.Spec.Copy() redactedSpec.CAConfig.SigningCAKey = nil + // the cert is not a secret, but if API users get the cluster spec and then update, + // then because the cert is included but not the key, the user can get update errors + // or unintended consequences (such as telling swarm to forget about the key so long + // as there is a corresponding external CA) + redactedSpec.CAConfig.SigningCACert = nil redactedRootCA := cluster.RootCA.Copy() redactedRootCA.CAKey = nil diff --git a/manager/controlapi/cluster_test.go b/manager/controlapi/cluster_test.go index 372165dfe6..7df369b4ea 100644 --- a/manager/controlapi/cluster_test.go +++ b/manager/controlapi/cluster_test.go @@ -440,8 +440,8 @@ func TestUpdateClusterRotateUnlockKey(t *testing.T) { } // root rotation tests have already been covered by ca_rotation_test.go - this test only makes sure that the function tested in those -// tests is actually called by `UpdateCluster`, and that the results of GetCluster and ListCluster have the rotation root CA key -// redacted +// tests is actually called by `UpdateCluster`, and that the results of GetCluster and ListCluster have the CA keys +// and the spec key and cert redacted func TestUpdateClusterRootRotation(t *testing.T) { ts := newTestServer(t) defer ts.Stop() @@ -464,26 +464,57 @@ func TestUpdateClusterRootRotation(t *testing.T) { }) require.NoError(t, err) - var gottenClusters []*api.Cluster + checkCluster := func() *api.Cluster { + response, err = ts.Client.GetCluster(context.Background(), &api.GetClusterRequest{ClusterID: cluster.ID}) + require.NoError(t, err) + require.NotNil(t, response.Cluster) - response, err = ts.Client.GetCluster(context.Background(), &api.GetClusterRequest{ClusterID: cluster.ID}) - require.NoError(t, err) - require.NotNil(t, response.Cluster) - gottenClusters = append(gottenClusters, response.Cluster) + listResponse, err := ts.Client.ListClusters(context.Background(), &api.ListClustersRequest{}) + require.NoError(t, err) + require.Len(t, listResponse.Clusters, 1) - listResponse, err := ts.Client.ListClusters(context.Background(), &api.ListClustersRequest{}) - require.NoError(t, err) - require.Len(t, listResponse.Clusters, 1) - gottenClusters = append(gottenClusters, listResponse.Clusters[0]) + require.Equal(t, response.Cluster, listResponse.Clusters[0]) - for _, c := range gottenClusters { + c := response.Cluster require.NotNil(t, c.RootCA.RootRotation) - // check signing key redaction + // check that all keys are redacted, and that the spec signing cert is also redacted (not because + // the cert is a secret, but because that makes it easier to get-and-update) require.Len(t, c.RootCA.CAKey, 0) require.Len(t, c.RootCA.RootRotation.CAKey, 0) require.Len(t, c.Spec.CAConfig.SigningCAKey, 0) + require.Len(t, c.Spec.CAConfig.SigningCACert, 0) + + return c + } + + getUnredactedRootCA := func() (rootCA *api.RootCA) { + ts.Store.View(func(tx store.ReadTx) { + c := store.GetCluster(tx, cluster.ID) + require.NotNil(t, c) + rootCA = &c.RootCA + }) + return } + + cluster = checkCluster() + unredactedRootCA := getUnredactedRootCA() + + // update something else, but make sure this doesn't the root CA rotation doesn't change + updatedSpec = cluster.Spec.Copy() + updatedSpec.CAConfig.NodeCertExpiry = gogotypes.DurationProto(time.Hour) + _, err = ts.Client.UpdateCluster(context.Background(), &api.UpdateClusterRequest{ + ClusterID: cluster.ID, + Spec: updatedSpec, + ClusterVersion: &cluster.Meta.Version, + }) + require.NoError(t, err) + + updatedCluster := checkCluster() + require.NotEqual(t, cluster.Spec.CAConfig.NodeCertExpiry, updatedCluster.Spec.CAConfig.NodeCertExpiry) + updatedUnredactedRootCA := getUnredactedRootCA() + + require.Equal(t, unredactedRootCA, updatedUnredactedRootCA) } func TestListClusters(t *testing.T) { diff --git a/manager/manager_test.go b/manager/manager_test.go index bef92df0a3..b6b8820331 100644 --- a/manager/manager_test.go +++ b/manager/manager_test.go @@ -31,8 +31,6 @@ import ( ) func TestManager(t *testing.T) { - ctx := context.Background() - temp, err := ioutil.TempFile("", "test-socket") assert.NoError(t, err) assert.NoError(t, temp.Close()) @@ -73,7 +71,7 @@ func TestManager(t *testing.T) { done := make(chan error) defer close(done) go func() { - done <- m.Run(ctx) + done <- m.Run(tc.Context) }() opts := []grpc.DialOption{ @@ -89,9 +87,9 @@ func TestManager(t *testing.T) { // We have to send a dummy request to verify if the connection is actually up. client := api.NewDispatcherClient(conn) - _, err = client.Heartbeat(ctx, &api.HeartbeatRequest{}) + _, err = client.Heartbeat(tc.Context, &api.HeartbeatRequest{}) assert.Equal(t, dispatcher.ErrNodeNotRegistered.Error(), grpc.ErrorDesc(err)) - _, err = client.Session(ctx, &api.SessionRequest{}) + _, err = client.Session(tc.Context, &api.SessionRequest{}) assert.NoError(t, err) // Try to have a client in a different org access this manager @@ -211,7 +209,7 @@ func TestManager(t *testing.T) { _, err = client.Heartbeat(context.Background(), &api.HeartbeatRequest{}) assert.Contains(t, grpc.ErrorDesc(err), "removed from swarm") - m.Stop(ctx, false) + m.Stop(tc.Context, false) // After stopping we should MAY receive an error from ListenAndServe if // all this happened before WaitForLeader completed, so don't check the @@ -221,8 +219,6 @@ func TestManager(t *testing.T) { // Tests locking and unlocking the manager and key rotations func TestManagerLockUnlock(t *testing.T) { - ctx := context.Background() - temp, err := ioutil.TempFile("", "test-manager-lock") require.NoError(t, err) require.NoError(t, temp.Close()) @@ -257,7 +253,7 @@ func TestManagerLockUnlock(t *testing.T) { done := make(chan error) defer close(done) go func() { - done <- m.Run(ctx) + done <- m.Run(tc.Context) }() opts := []grpc.DialOption{ @@ -277,7 +273,7 @@ func TestManagerLockUnlock(t *testing.T) { client := api.NewControlClient(conn) require.NoError(t, testutils.PollFuncWithTimeout(nil, func() error { - resp, err := client.ListClusters(ctx, &api.ListClustersRequest{}) + resp, err := client.ListClusters(tc.Context, &api.ListClustersRequest{}) if err != nil { return err } @@ -303,13 +299,13 @@ func TestManagerLockUnlock(t *testing.T) { // update the lock key - this may fail due to update out of sequence errors, so try again for { - getResp, err := client.GetCluster(ctx, &api.GetClusterRequest{ClusterID: cluster.ID}) + getResp, err := client.GetCluster(tc.Context, &api.GetClusterRequest{ClusterID: cluster.ID}) require.NoError(t, err) cluster = getResp.Cluster spec := cluster.Spec.Copy() spec.EncryptionConfig.AutoLockManagers = true - updateResp, err := client.UpdateCluster(ctx, &api.UpdateClusterRequest{ + updateResp, err := client.UpdateCluster(tc.Context, &api.UpdateClusterRequest{ ClusterID: cluster.ID, ClusterVersion: &cluster.Meta.Version, Spec: spec, @@ -326,7 +322,7 @@ func TestManagerLockUnlock(t *testing.T) { require.NoError(t, err) caConn := api.NewCAClient(conn) - unlockKeyResp, err := caConn.GetUnlockKey(ctx, &api.GetUnlockKeyRequest{}) + unlockKeyResp, err := caConn.GetUnlockKey(tc.Context, &api.GetUnlockKeyRequest{}) require.NoError(t, err) // this should update the TLS key, rotate the DEK, and finish snapshotting @@ -377,13 +373,13 @@ func TestManagerLockUnlock(t *testing.T) { // update the lock key to nil for i := 0; i < 3; i++ { - getResp, err := client.GetCluster(ctx, &api.GetClusterRequest{ClusterID: cluster.ID}) + getResp, err := client.GetCluster(tc.Context, &api.GetClusterRequest{ClusterID: cluster.ID}) require.NoError(t, err) cluster = getResp.Cluster spec := cluster.Spec.Copy() spec.EncryptionConfig.AutoLockManagers = false - _, err = client.UpdateCluster(ctx, &api.UpdateClusterRequest{ + _, err = client.UpdateCluster(tc.Context, &api.UpdateClusterRequest{ ClusterID: cluster.ID, ClusterVersion: &cluster.Meta.Version, Spec: spec, @@ -420,7 +416,7 @@ func TestManagerLockUnlock(t *testing.T) { require.NotNil(t, unencryptedDEK) require.Equal(t, currentDEK, unencryptedDEK) - m.Stop(ctx, false) + m.Stop(tc.Context, false) // After stopping we should MAY receive an error from ListenAndServe if // all this happened before WaitForLeader completed, so don't check the @@ -431,8 +427,6 @@ func TestManagerLockUnlock(t *testing.T) { // If the root CA material is updated in the memory store, a manager will update its own // security configs even if it's "not the leader" (which we will fake by calling `becomeFollower`) func TestManagerUpdatesSecurityConfig(t *testing.T) { - ctx := context.Background() - temp, err := ioutil.TempFile("", "test-manager-update-security-config") require.NoError(t, err) require.NoError(t, temp.Close()) @@ -466,7 +460,7 @@ func TestManagerUpdatesSecurityConfig(t *testing.T) { done := make(chan error) defer close(done) go func() { - done <- m.Run(ctx) + done <- m.Run(tc.Context) }() // wait until the CA server is running @@ -484,7 +478,7 @@ func TestManagerUpdatesSecurityConfig(t *testing.T) { client := api.NewCAClient(conn) require.NoError(t, testutils.PollFuncWithTimeout(nil, func() error { - ctx, _ := context.WithTimeout(context.Background(), 500*time.Millisecond) + ctx, _ := context.WithTimeout(tc.Context, 500*time.Millisecond) _, err := client.GetRootCACertificate(ctx, &api.GetRootCACertificateRequest{}) return err }, time.Second)) @@ -530,7 +524,7 @@ func TestManagerUpdatesSecurityConfig(t *testing.T) { return nil }, 1*time.Second)) - m.Stop(ctx, false) + m.Stop(tc.Context, false) // After stopping we should MAY receive an error from ListenAndServe if // all this happened before WaitForLeader completed, so don't check the @@ -540,8 +534,6 @@ func TestManagerUpdatesSecurityConfig(t *testing.T) { // Tests manager rotates encryption of root key data in the raft store func TestManagerEncryptsDecryptsRootKeyMaterial(t *testing.T) { - ctx := context.Background() - tc := cautils.NewTestCA(t) defer tc.Stop() @@ -579,7 +571,7 @@ func TestManagerEncryptsDecryptsRootKeyMaterial(t *testing.T) { require.NotNil(t, m) go func() { - done <- m.Run(ctx) + done <- m.Run(tc.Context) }() } @@ -606,7 +598,7 @@ func TestManagerEncryptsDecryptsRootKeyMaterial(t *testing.T) { defer os.Unsetenv(ca.PassphraseENVVar) // restart - m.Stop(ctx, false) + m.Stop(tc.Context, false) <-done startManager() @@ -635,7 +627,7 @@ func TestManagerEncryptsDecryptsRootKeyMaterial(t *testing.T) { defer os.Unsetenv(ca.PassphraseENVVarPrev) // restart - m.Stop(ctx, false) + m.Stop(tc.Context, false) <-done startManager() @@ -687,11 +679,11 @@ G80TfNRRr/qdB9hLwfyOyk2tBipkAgs6cl+CZAaqx3k= })) // restart - m.Stop(ctx, false) + m.Stop(tc.Context, false) <-done startManager() require.NoError(t, pollDecrypted()) - m.Stop(ctx, false) + m.Stop(tc.Context, false) <-done } diff --git a/manager/scheduler/filter.go b/manager/scheduler/filter.go index 6ce3f2c8b8..36b601c4b4 100644 --- a/manager/scheduler/filter.go +++ b/manager/scheduler/filter.go @@ -128,7 +128,7 @@ func (f *PluginFilter) SetTask(t *api.Task) bool { } } - if (c != nil && volumeTemplates) || len(t.Networks) > 0 { + if (c != nil && volumeTemplates) || len(t.Networks) > 0 || t.Spec.LogDriver != nil { f.t = t return true } @@ -153,7 +153,7 @@ func (f *PluginFilter) Check(n *NodeInfo) bool { if container != nil { for _, mount := range container.Mounts { if referencesVolumePlugin(mount) { - if !f.pluginExistsOnNode("Volume", mount.VolumeOptions.DriverConfig.Name, nodePlugins) { + if _, exists := f.pluginExistsOnNode("Volume", mount.VolumeOptions.DriverConfig.Name, nodePlugins); !exists { return false } } @@ -163,22 +163,34 @@ func (f *PluginFilter) Check(n *NodeInfo) bool { // Check if all network plugins required by task are installed on node for _, tn := range f.t.Networks { if tn.Network != nil && tn.Network.DriverState != nil && tn.Network.DriverState.Name != "" { - if !f.pluginExistsOnNode("Network", tn.Network.DriverState.Name, nodePlugins) { + if _, exists := f.pluginExistsOnNode("Network", tn.Network.DriverState.Name, nodePlugins); !exists { return false } } } + + if f.t.Spec.LogDriver != nil { + // If there are no log driver types in the list at all, most likely this is + // an older daemon that did not report this information. In this case don't filter + if typeFound, exists := f.pluginExistsOnNode("Log", f.t.Spec.LogDriver.Name, nodePlugins); !exists && typeFound { + return false + } + } return true } // pluginExistsOnNode returns true if the (pluginName, pluginType) pair is present in nodePlugins -func (f *PluginFilter) pluginExistsOnNode(pluginType string, pluginName string, nodePlugins []api.PluginDescription) bool { +func (f *PluginFilter) pluginExistsOnNode(pluginType string, pluginName string, nodePlugins []api.PluginDescription) (bool, bool) { + var typeFound bool + for _, np := range nodePlugins { if pluginType != np.Type { continue } + typeFound = true + if pluginName == np.Name { - return true + return true, true } // This does not use the reference package to avoid the // overhead of parsing references as part of the scheduling @@ -186,10 +198,10 @@ func (f *PluginFilter) pluginExistsOnNode(pluginType string, pluginName string, // strict subset of the reference grammar that is always // name:tag. if strings.HasPrefix(np.Name, pluginName) && np.Name[len(pluginName):] == ":latest" { - return true + return true, true } } - return false + return typeFound, false } // Explain returns an explanation of a failure. diff --git a/manager/scheduler/nodeset.go b/manager/scheduler/nodeset.go index 7f899d8b26..b83704a18d 100644 --- a/manager/scheduler/nodeset.go +++ b/manager/scheduler/nodeset.go @@ -4,7 +4,6 @@ import ( "container/heap" "errors" "strings" - "time" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/manager/constraint" @@ -32,16 +31,6 @@ func (ns *nodeSet) nodeInfo(nodeID string) (NodeInfo, error) { // addOrUpdateNode sets the number of tasks for a given node. It adds the node // to the set if it wasn't already tracked. func (ns *nodeSet) addOrUpdateNode(n NodeInfo) { - if n.Tasks == nil { - n.Tasks = make(map[string]*api.Task) - } - if n.ActiveTasksCountByService == nil { - n.ActiveTasksCountByService = make(map[string]int) - } - if n.recentFailures == nil { - n.recentFailures = make(map[string][]time.Time) - } - ns.nodes[n.ID] = n } diff --git a/manager/scheduler/scheduler.go b/manager/scheduler/scheduler.go index 9ff921fd5b..07d9b0458c 100644 --- a/manager/scheduler/scheduler.go +++ b/manager/scheduler/scheduler.go @@ -315,25 +315,32 @@ func (s *Scheduler) deleteTask(t *api.Task) bool { } func (s *Scheduler) createOrUpdateNode(n *api.Node) { - nodeInfo, _ := s.nodeSet.nodeInfo(n.ID) + nodeInfo, nodeInfoErr := s.nodeSet.nodeInfo(n.ID) var resources *api.Resources if n.Description != nil && n.Description.Resources != nil { resources = n.Description.Resources.Copy() // reconcile resources by looping over all tasks in this node - for _, task := range nodeInfo.Tasks { - reservations := taskReservations(task.Spec) + if nodeInfoErr == nil { + for _, task := range nodeInfo.Tasks { + reservations := taskReservations(task.Spec) - resources.MemoryBytes -= reservations.MemoryBytes - resources.NanoCPUs -= reservations.NanoCPUs + resources.MemoryBytes -= reservations.MemoryBytes + resources.NanoCPUs -= reservations.NanoCPUs - genericresource.ConsumeNodeResources(&resources.Generic, - task.AssignedGenericResources) + genericresource.ConsumeNodeResources(&resources.Generic, + task.AssignedGenericResources) + } } } else { resources = &api.Resources{} } - nodeInfo.Node = n - nodeInfo.AvailableResources = resources + + if nodeInfoErr != nil { + nodeInfo = newNodeInfo(n, nil, *resources) + } else { + nodeInfo.Node = n + nodeInfo.AvailableResources = resources + } s.nodeSet.addOrUpdateNode(nodeInfo) } diff --git a/manager/scheduler/scheduler_test.go b/manager/scheduler/scheduler_test.go index 32825e1144..82a247ff18 100644 --- a/manager/scheduler/scheduler_test.go +++ b/manager/scheduler/scheduler_test.go @@ -2178,6 +2178,10 @@ func TestSchedulerPluginConstraint(t *testing.T) { Type: "Volume", Name: "plugin1", }, + { + Type: "Log", + Name: "default", + }, }, }, }, @@ -2205,6 +2209,10 @@ func TestSchedulerPluginConstraint(t *testing.T) { Type: "Volume", Name: "plugin2", }, + { + Type: "Log", + Name: "default", + }, }, }, }, @@ -2232,6 +2240,33 @@ func TestSchedulerPluginConstraint(t *testing.T) { Type: "Network", Name: "plugin1", }, + { + Type: "Log", + Name: "default", + }, + }, + }, + }, + Status: api.NodeStatus{ + State: api.NodeStatus_READY, + }, + } + + // Node4: log plugin1 + n4 := &api.Node{ + ID: "node4_ID", + Spec: api.NodeSpec{ + Annotations: api.Annotations{ + Name: "node4", + }, + }, + Description: &api.NodeDescription{ + Engine: &api.EngineDescription{ + Plugins: []api.PluginDescription{ + { + Type: "Log", + Name: "plugin1", + }, }, }, }, @@ -2346,6 +2381,40 @@ func TestSchedulerPluginConstraint(t *testing.T) { State: api.TaskStatePending, }, } + // Task4: log plugin1 + t4 := &api.Task{ + ID: "task4_ID", + DesiredState: api.TaskStateRunning, + Spec: api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{}, + }, + LogDriver: &api.Driver{Name: "plugin1"}, + }, + ServiceAnnotations: api.Annotations{ + Name: "task4", + }, + Status: api.TaskStatus{ + State: api.TaskStatePending, + }, + } + // Task5: log plugin1 + t5 := &api.Task{ + ID: "task5_ID", + DesiredState: api.TaskStateRunning, + Spec: api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{}, + }, + LogDriver: &api.Driver{Name: "plugin1"}, + }, + ServiceAnnotations: api.Annotations{ + Name: "task5", + }, + Status: api.TaskStatus{ + State: api.TaskStatePending, + }, + } s := store.NewMemoryStore(nil) assert.NotNil(t, s) @@ -2418,6 +2487,38 @@ func TestSchedulerPluginConstraint(t *testing.T) { assignment2 := watchAssignment(t, watch) assert.Equal(t, assignment2.ID, "task3_ID") assert.Equal(t, assignment2.NodeID, "node3_ID") + + // Create t4; it should stay in the pending state because there is + // no node that with log plugin `plugin1` + err = s.Update(func(tx store.Tx) error { + assert.NoError(t, store.CreateTask(tx, t4)) + return nil + }) + assert.NoError(t, err) + + // check that t4 has been assigned + failure2 := watchAssignmentFailure(t, watch) + assert.Equal(t, "no suitable node (missing plugin on 3 nodes)", failure2.Status.Message) + + err = s.Update(func(tx store.Tx) error { + assert.NoError(t, store.CreateNode(tx, n4)) + return nil + }) + assert.NoError(t, err) + + // Check that t4 has been assigned + assignment3 := watchAssignment(t, watch) + assert.Equal(t, assignment3.ID, "task4_ID") + assert.Equal(t, assignment3.NodeID, "node4_ID") + + err = s.Update(func(tx store.Tx) error { + assert.NoError(t, store.CreateTask(tx, t5)) + return nil + }) + assert.NoError(t, err) + assignment4 := watchAssignment(t, watch) + assert.Equal(t, assignment4.ID, "task5_ID") + assert.Equal(t, assignment4.NodeID, "node4_ID") } func BenchmarkScheduler1kNodes1kTasks(b *testing.B) { @@ -2663,8 +2764,6 @@ func TestSchedulerHostPort(t *testing.T) { // Add initial node and task assert.NoError(t, store.CreateTask(tx, task1)) assert.NoError(t, store.CreateTask(tx, task2)) - assert.NoError(t, store.CreateNode(tx, node1)) - assert.NoError(t, store.CreateNode(tx, node2)) return nil }) assert.NoError(t, err) @@ -2679,6 +2778,18 @@ func TestSchedulerHostPort(t *testing.T) { }() defer scheduler.Stop() + // Tasks shouldn't be scheduled because there are no nodes. + watchAssignmentFailure(t, watch) + watchAssignmentFailure(t, watch) + + err = s.Update(func(tx store.Tx) error { + // Add initial node and task + assert.NoError(t, store.CreateNode(tx, node1)) + assert.NoError(t, store.CreateNode(tx, node2)) + return nil + }) + assert.NoError(t, err) + // Tasks 1 and 2 should be assigned to different nodes. assignment1 := watchAssignment(t, watch) assignment2 := watchAssignment(t, watch) diff --git a/template/context.go b/template/context.go index e3c3aab113..d26e155be4 100644 --- a/template/context.go +++ b/template/context.go @@ -3,13 +3,22 @@ package template import ( "bytes" "fmt" + "strings" + "text/template" + "github.com/docker/swarmkit/agent/configs" + "github.com/docker/swarmkit/agent/exec" + "github.com/docker/swarmkit/agent/secrets" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/api/naming" + "github.com/pkg/errors" ) // Context defines the strict set of values that can be injected into a // template expression in SwarmKit data structure. +// NOTE: Be very careful adding any fields to this structure with types +// that have methods defined on them. The template would be able to +// invoke those methods. type Context struct { Service struct { ID string @@ -58,7 +67,118 @@ func NewContextFromTask(t *api.Task) (ctx Context) { // Expand treats the string s as a template and populates it with values from // the context. func (ctx *Context) Expand(s string) (string, error) { - tmpl, err := newTemplate(s) + tmpl, err := newTemplate(s, nil) + if err != nil { + return s, err + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, ctx); err != nil { + return s, err + } + + return buf.String(), nil +} + +// PayloadContext provides a context for expanding a config or secret payload. +// NOTE: Be very careful adding any fields to this structure with types +// that have methods defined on them. The template would be able to +// invoke those methods. +type PayloadContext struct { + Context + + t *api.Task + restrictedSecrets exec.SecretGetter + restrictedConfigs exec.ConfigGetter +} + +func (ctx PayloadContext) secretGetter(target string) (string, error) { + if ctx.restrictedSecrets == nil { + return "", errors.New("secrets unavailable") + } + + container := ctx.t.Spec.GetContainer() + if container == nil { + return "", errors.New("task is not a container") + } + + for _, secretRef := range container.Secrets { + file := secretRef.GetFile() + if file != nil && file.Name == target { + secret, err := ctx.restrictedSecrets.Get(secretRef.SecretID) + if err != nil { + return "", err + } + return string(secret.Spec.Data), nil + } + } + + return "", errors.Errorf("secret target %s not found", target) +} + +func (ctx PayloadContext) configGetter(target string) (string, error) { + if ctx.restrictedConfigs == nil { + return "", errors.New("configs unavailable") + } + + container := ctx.t.Spec.GetContainer() + if container == nil { + return "", errors.New("task is not a container") + } + + for _, configRef := range container.Configs { + file := configRef.GetFile() + if file != nil && file.Name == target { + config, err := ctx.restrictedConfigs.Get(configRef.ConfigID) + if err != nil { + return "", err + } + return string(config.Spec.Data), nil + } + } + + return "", errors.Errorf("config target %s not found", target) +} + +func (ctx PayloadContext) envGetter(variable string) (string, error) { + container := ctx.t.Spec.GetContainer() + if container == nil { + return "", errors.New("task is not a container") + } + + for _, env := range container.Env { + parts := strings.SplitN(env, "=", 2) + + if len(parts) > 1 && parts[0] == variable { + return parts[1], nil + } + } + return "", nil +} + +// NewPayloadContextFromTask returns a new template context from the data +// available in the task. This context also provides access to the configs +// and secrets that the task has access to. The provided context can then +// be used to populate runtime values in a templated config or secret. +func NewPayloadContextFromTask(t *api.Task, dependencies exec.DependencyGetter) (ctx PayloadContext) { + return PayloadContext{ + Context: NewContextFromTask(t), + t: t, + restrictedSecrets: secrets.Restrict(dependencies.Secrets(), t), + restrictedConfigs: configs.Restrict(dependencies.Configs(), t), + } +} + +// Expand treats the string s as a template and populates it with values from +// the context. +func (ctx *PayloadContext) Expand(s string) (string, error) { + funcMap := template.FuncMap{ + "secret": ctx.secretGetter, + "config": ctx.configGetter, + "env": ctx.envGetter, + } + + tmpl, err := newTemplate(s, funcMap) if err != nil { return s, err } diff --git a/template/expand.go b/template/expand.go index 75fbc09aee..e45c36252d 100644 --- a/template/expand.go +++ b/template/expand.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/docker/swarmkit/agent/exec" "github.com/docker/swarmkit/api" "github.com/pkg/errors" ) @@ -116,3 +117,45 @@ func expandEnv(ctx Context, values []string) ([]string, error) { return result, nil } + +func expandPayload(ctx PayloadContext, payload []byte) ([]byte, error) { + result, err := ctx.Expand(string(payload)) + if err != nil { + return payload, err + } + return []byte(result), nil +} + +// ExpandSecretSpec expands the template inside the secret payload, if any. +// Templating is evaluated on the agent-side. +func ExpandSecretSpec(s *api.Secret, t *api.Task, dependencies exec.DependencyGetter) (*api.SecretSpec, error) { + if s.Spec.Templating == nil { + return &s.Spec, nil + } + if s.Spec.Templating.Name == "golang" { + ctx := NewPayloadContextFromTask(t, dependencies) + secretSpec := s.Spec.Copy() + + var err error + secretSpec.Data, err = expandPayload(ctx, secretSpec.Data) + return secretSpec, err + } + return &s.Spec, errors.New("unrecognized template type") +} + +// ExpandConfigSpec expands the template inside the config payload, if any. +// Templating is evaluated on the agent-side. +func ExpandConfigSpec(c *api.Config, t *api.Task, dependencies exec.DependencyGetter) (*api.ConfigSpec, error) { + if c.Spec.Templating == nil { + return &c.Spec, nil + } + if c.Spec.Templating.Name == "golang" { + ctx := NewPayloadContextFromTask(t, dependencies) + configSpec := c.Spec.Copy() + + var err error + configSpec.Data, err = expandPayload(ctx, configSpec.Data) + return configSpec, err + } + return &c.Spec, errors.New("unrecognized template type") +} diff --git a/template/getter.go b/template/getter.go new file mode 100644 index 0000000000..f06c438c25 --- /dev/null +++ b/template/getter.go @@ -0,0 +1,98 @@ +package template + +import ( + "github.com/docker/swarmkit/agent/exec" + "github.com/docker/swarmkit/api" + "github.com/pkg/errors" +) + +type templatedSecretGetter struct { + dependencies exec.DependencyGetter + t *api.Task +} + +// NewTemplatedSecretGetter returns a SecretGetter that evaluates templates. +func NewTemplatedSecretGetter(dependencies exec.DependencyGetter, t *api.Task) exec.SecretGetter { + return templatedSecretGetter{dependencies: dependencies, t: t} +} + +func (t templatedSecretGetter) Get(secretID string) (*api.Secret, error) { + if t.dependencies == nil { + return nil, errors.New("no secret provider available") + } + + secrets := t.dependencies.Secrets() + if secrets == nil { + return nil, errors.New("no secret provider available") + } + + secret, err := secrets.Get(secretID) + if err != nil { + return secret, err + } + + newSpec, err := ExpandSecretSpec(secret, t.t, t.dependencies) + if err != nil { + return secret, errors.Wrapf(err, "failed to expand templated secret %s", secretID) + } + + secretCopy := *secret + secretCopy.Spec = *newSpec + return &secretCopy, nil +} + +type templatedConfigGetter struct { + dependencies exec.DependencyGetter + t *api.Task +} + +// NewTemplatedConfigGetter returns a ConfigGetter that evaluates templates. +func NewTemplatedConfigGetter(dependencies exec.DependencyGetter, t *api.Task) exec.ConfigGetter { + return templatedConfigGetter{dependencies: dependencies, t: t} +} + +func (t templatedConfigGetter) Get(configID string) (*api.Config, error) { + if t.dependencies == nil { + return nil, errors.New("no config provider available") + } + + configs := t.dependencies.Configs() + if configs == nil { + return nil, errors.New("no config provider available") + } + + config, err := configs.Get(configID) + if err != nil { + return config, err + } + + newSpec, err := ExpandConfigSpec(config, t.t, t.dependencies) + if err != nil { + return config, errors.Wrapf(err, "failed to expand templated config %s", configID) + } + + configCopy := *config + configCopy.Spec = *newSpec + return &configCopy, nil +} + +type templatedDependencyGetter struct { + secrets exec.SecretGetter + configs exec.ConfigGetter +} + +// NewTemplatedDependencyGetter returns a DependencyGetter that evaluates templates. +func NewTemplatedDependencyGetter(dependencies exec.DependencyGetter, t *api.Task) exec.DependencyGetter { + return templatedDependencyGetter{ + secrets: NewTemplatedSecretGetter(dependencies, t), + configs: NewTemplatedConfigGetter(dependencies, t), + } +} + +func (t templatedDependencyGetter) Secrets() exec.SecretGetter { + return t.secrets +} + +func (t templatedDependencyGetter) Configs() exec.ConfigGetter { + return t.configs +} diff --git a/template/getter_test.go b/template/getter_test.go new file mode 100644 index 0000000000..88ea0570ec --- /dev/null +++ b/template/getter_test.go @@ -0,0 +1,506 @@ +package template + +import ( + "testing" + + "github.com/docker/swarmkit/agent" + "github.com/docker/swarmkit/api" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTemplatedSecret(t *testing.T) { + templatedSecret := &api.Secret{ + ID: "templatedsecret", + } + + referencedSecret := &api.Secret{ + ID: "referencedsecret", + Spec: api.SecretSpec{ + Data: []byte("mysecret"), + }, + } + referencedConfig := &api.Config{ + ID: "referencedconfig", + Spec: api.ConfigSpec{ + Data: []byte("myconfig"), + }, + } + + type testCase struct { + desc string + secretSpec api.SecretSpec + task *api.Task + expected string + expectedErr string + } + + testCases := []testCase{ + { + desc: "Test expansion of task context", + secretSpec: api.SecretSpec{ + Data: []byte("SERVICE_ID={{.Service.ID}}\n" + + "SERVICE_NAME={{.Service.Name}}\n" + + "TASK_ID={{.Task.ID}}\n" + + "TASK_NAME={{.Task.Name}}\n" + + "NODE_ID={{.Node.ID}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expected: "SERVICE_ID=serviceID\n" + + "SERVICE_NAME=serviceName\n" + + "TASK_ID=taskID\n" + + "TASK_NAME=serviceName.10.taskID\n" + + "NODE_ID=nodeID\n", + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Secrets: []*api.SecretReference{ + { + SecretID: "templatedsecret", + SecretName: "templatedsecretname", + }, + }, + }, + }, + } + }), + }, + { + desc: "Test expansion of secret, by target", + secretSpec: api.SecretSpec{ + Data: []byte("SECRET_VAL={{secret \"referencedsecrettarget\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expected: "SECRET_VAL=mysecret\n", + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Secrets: []*api.SecretReference{ + { + SecretID: "templatedsecret", + SecretName: "templatedsecretname", + }, + { + SecretID: "referencedsecret", + SecretName: "referencedsecretname", + Target: &api.SecretReference_File{ + File: &api.FileTarget{ + Name: "referencedsecrettarget", + UID: "0", + GID: "0", + Mode: 0666, + }, + }, + }, + }, + }, + }, + } + }), + }, + { + desc: "Test expansion of config, by target", + secretSpec: api.SecretSpec{ + Data: []byte("CONFIG_VAL={{config \"referencedconfigtarget\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expected: "CONFIG_VAL=myconfig\n", + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Secrets: []*api.SecretReference{ + { + SecretID: "templatedsecret", + SecretName: "templatedsecretname", + }, + }, + Configs: []*api.ConfigReference{ + { + ConfigID: "referencedconfig", + ConfigName: "referencedconfigname", + Target: &api.ConfigReference_File{ + File: &api.FileTarget{ + Name: "referencedconfigtarget", + UID: "0", + GID: "0", + Mode: 0666, + }, + }, + }, + }, + }, + }, + } + }), + }, + { + desc: "Test expansion of secret not available to task", + secretSpec: api.SecretSpec{ + Data: []byte("SECRET_VAL={{secret \"unknowntarget\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expectedErr: `failed to expand templated secret templatedsecret: template: expansion:1:13: executing "expansion" at : error calling secret: secret target unknowntarget not found`, + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Secrets: []*api.SecretReference{ + { + SecretID: "templatedsecret", + SecretName: "templatedsecretname", + }, + }, + }, + }, + } + }), + }, + { + desc: "Test expansion of config not available to task", + secretSpec: api.SecretSpec{ + Data: []byte("CONFIG_VAL={{config \"unknowntarget\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expectedErr: `failed to expand templated secret templatedsecret: template: expansion:1:13: executing "expansion" at : error calling config: config target unknowntarget not found`, + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Secrets: []*api.SecretReference{ + { + SecretID: "templatedsecret", + SecretName: "templatedsecretname", + }, + }, + }, + }, + } + }), + }, + { + desc: "Test that expansion of the same secret avoids recursion", + secretSpec: api.SecretSpec{ + Data: []byte("SECRET_VAL={{secret \"templatedsecrettarget\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expected: "SECRET_VAL=SECRET_VAL={{secret \"templatedsecrettarget\"}}\n\n", + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Secrets: []*api.SecretReference{ + { + SecretID: "templatedsecret", + SecretName: "templatedsecretname", + Target: &api.SecretReference_File{ + File: &api.FileTarget{ + Name: "templatedsecrettarget", + UID: "0", + GID: "0", + Mode: 0666, + }, + }, + }, + }, + }, + }, + } + }), + }, + { + desc: "Test env", + secretSpec: api.SecretSpec{ + Data: []byte("ENV VALUE={{env \"foo\"}}\n" + + "DOES NOT EXIST={{env \"badname\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expected: "ENV VALUE=bar\n" + + "DOES NOT EXIST=\n", + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Secrets: []*api.SecretReference{ + { + SecretID: "templatedsecret", + SecretName: "templatedsecretname", + }, + }, + Env: []string{"foo=bar"}, + }, + }, + } + }), + }, + } + + for _, testCase := range testCases { + templatedSecret.Spec = testCase.secretSpec + + dependencyManager := agent.NewDependencyManager() + dependencyManager.Secrets().Add(*templatedSecret, *referencedSecret) + dependencyManager.Configs().Add(*referencedConfig) + + templatedDependencies := NewTemplatedDependencyGetter(agent.Restrict(dependencyManager, testCase.task), testCase.task) + expandedSecret, err := templatedDependencies.Secrets().Get("templatedsecret") + + if testCase.expectedErr != "" { + assert.EqualError(t, err, testCase.expectedErr) + } else { + assert.NoError(t, err) + require.NotNil(t, expandedSecret) + assert.Equal(t, testCase.expected, string(expandedSecret.Spec.Data), testCase.desc) + } + } +} + +func TestTemplatedConfig(t *testing.T) { + templatedConfig := &api.Config{ + ID: "templatedconfig", + } + + referencedSecret := &api.Secret{ + ID: "referencedsecret", + Spec: api.SecretSpec{ + Data: []byte("mysecret"), + }, + } + referencedConfig := &api.Config{ + ID: "referencedconfig", + Spec: api.ConfigSpec{ + Data: []byte("myconfig"), + }, + } + + type testCase struct { + desc string + configSpec api.ConfigSpec + task *api.Task + expected string + expectedErr string + } + + testCases := []testCase{ + { + desc: "Test expansion of task context", + configSpec: api.ConfigSpec{ + Data: []byte("SERVICE_ID={{.Service.ID}}\n" + + "SERVICE_NAME={{.Service.Name}}\n" + + "TASK_ID={{.Task.ID}}\n" + + "TASK_NAME={{.Task.Name}}\n" + + "NODE_ID={{.Node.ID}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expected: "SERVICE_ID=serviceID\n" + + "SERVICE_NAME=serviceName\n" + + "TASK_ID=taskID\n" + + "TASK_NAME=serviceName.10.taskID\n" + + "NODE_ID=nodeID\n", + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Configs: []*api.ConfigReference{ + { + ConfigID: "templatedconfig", + ConfigName: "templatedconfigname", + }, + }, + }, + }, + } + }), + }, + { + desc: "Test expansion of secret, by target", + configSpec: api.ConfigSpec{ + Data: []byte("SECRET_VAL={{secret \"referencedsecrettarget\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expected: "SECRET_VAL=mysecret\n", + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Secrets: []*api.SecretReference{ + { + SecretID: "referencedsecret", + SecretName: "referencedsecretname", + Target: &api.SecretReference_File{ + File: &api.FileTarget{ + Name: "referencedsecrettarget", + UID: "0", + GID: "0", + Mode: 0666, + }, + }, + }, + }, + Configs: []*api.ConfigReference{ + { + ConfigID: "templatedconfig", + ConfigName: "templatedconfigname", + }, + }, + }, + }, + } + }), + }, + { + desc: "Test expansion of config, by target", + configSpec: api.ConfigSpec{ + Data: []byte("CONFIG_VAL={{config \"referencedconfigtarget\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expected: "CONFIG_VAL=myconfig\n", + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Configs: []*api.ConfigReference{ + { + ConfigID: "templatedconfig", + ConfigName: "templatedconfigname", + }, + { + ConfigID: "referencedconfig", + ConfigName: "referencedconfigname", + Target: &api.ConfigReference_File{ + File: &api.FileTarget{ + Name: "referencedconfigtarget", + UID: "0", + GID: "0", + Mode: 0666, + }, + }, + }, + }, + }, + }, + } + }), + }, + { + desc: "Test expansion of secret not available to task", + configSpec: api.ConfigSpec{ + Data: []byte("SECRET_VAL={{secret \"unknowntarget\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expectedErr: `failed to expand templated config templatedconfig: template: expansion:1:13: executing "expansion" at : error calling secret: secret target unknowntarget not found`, + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Configs: []*api.ConfigReference{ + { + ConfigID: "templatedconfig", + ConfigName: "templatedconfigname", + }, + }, + }, + }, + } + }), + }, + { + desc: "Test expansion of config not available to task", + configSpec: api.ConfigSpec{ + Data: []byte("CONFIG_VAL={{config \"unknowntarget\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expectedErr: `failed to expand templated config templatedconfig: template: expansion:1:13: executing "expansion" at : error calling config: config target unknowntarget not found`, + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Configs: []*api.ConfigReference{ + { + ConfigID: "templatedconfig", + ConfigName: "templatedconfigname", + }, + }, + }, + }, + } + }), + }, + { + desc: "Test that expansion of the same config avoids recursion", + configSpec: api.ConfigSpec{ + Data: []byte("CONFIG_VAL={{config \"templatedconfigtarget\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expected: "CONFIG_VAL=CONFIG_VAL={{config \"templatedconfigtarget\"}}\n\n", + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Configs: []*api.ConfigReference{ + { + ConfigID: "templatedconfig", + ConfigName: "templatedconfigname", + Target: &api.ConfigReference_File{ + File: &api.FileTarget{ + Name: "templatedconfigtarget", + UID: "0", + GID: "0", + Mode: 0666, + }, + }, + }, + }, + }, + }, + } + }), + }, + { + desc: "Test env", + configSpec: api.ConfigSpec{ + Data: []byte("ENV VALUE={{env \"foo\"}}\n" + + "DOES NOT EXIST={{env \"badname\"}}\n"), + Templating: &api.Driver{Name: "golang"}, + }, + expected: "ENV VALUE=bar\n" + + "DOES NOT EXIST=\n", + task: modifyTask(func(t *api.Task) { + t.Spec = api.TaskSpec{ + Runtime: &api.TaskSpec_Container{ + Container: &api.ContainerSpec{ + Configs: []*api.ConfigReference{ + { + ConfigID: "templatedconfig", + ConfigName: "templatedconfigname", + }, + }, + Env: []string{"foo=bar"}, + }, + }, + } + }), + }, + } + + for _, testCase := range testCases { + templatedConfig.Spec = testCase.configSpec + + dependencyManager := agent.NewDependencyManager() + dependencyManager.Configs().Add(*templatedConfig, *referencedConfig) + dependencyManager.Secrets().Add(*referencedSecret) + + templatedDependencies := NewTemplatedDependencyGetter(agent.Restrict(dependencyManager, testCase.task), testCase.task) + expandedConfig, err := templatedDependencies.Configs().Get("templatedconfig") + + if testCase.expectedErr != "" { + assert.EqualError(t, err, testCase.expectedErr) + } else { + assert.NoError(t, err) + require.NotNil(t, expandedConfig) + assert.Equal(t, testCase.expected, string(expandedConfig.Spec.Data), testCase.desc) + } + } +} diff --git a/template/template.go b/template/template.go index 9f1517c662..fc375b819c 100644 --- a/template/template.go +++ b/template/template.go @@ -13,6 +13,10 @@ var funcMap = template.FuncMap{ }, } -func newTemplate(s string) (*template.Template, error) { - return template.New("expansion").Option("missingkey=error").Funcs(funcMap).Parse(s) +func newTemplate(s string, extraFuncs template.FuncMap) (*template.Template, error) { + tmpl := template.New("expansion").Option("missingkey=error").Funcs(funcMap) + if len(extraFuncs) != 0 { + tmpl = tmpl.Funcs(extraFuncs) + } + return tmpl.Parse(s) }