diff --git a/minio/check_config.go b/minio/check_config.go index 9414abc5..ab08f0be 100644 --- a/minio/check_config.go +++ b/minio/check_config.go @@ -1,11 +1,32 @@ package minio import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -// BucketConfig creates a new config for minio buckets +// ConfigError represents an error that occurred during configuration +type ConfigError struct { + Field string + Message string +} + +func (e *ConfigError) Error() string { + return fmt.Sprintf("configuration error for field %q: %s", e.Field, e.Message) +} + +// getOptionalField safely gets an optional field from the ResourceData with a default value +func getOptionalField(d *schema.ResourceData, field string, defaultValue interface{}) interface{} { + if v, ok := d.GetOk(field); ok { + return v + } + return defaultValue +} + +// BucketConfig creates a new configuration for MinIO buckets. +// It handles the basic bucket configuration including ACL, prefixes, and object locking. func BucketConfig(d *schema.ResourceData, meta interface{}) *S3MinioBucket { m := meta.(*S3MinioClient) @@ -14,26 +35,28 @@ func BucketConfig(d *schema.ResourceData, meta interface{}) *S3MinioBucket { MinioAdmin: m.S3Admin, MinioRegion: m.S3Region, MinioAccess: m.S3UserAccess, - MinioBucket: d.Get("bucket").(string), - MinioBucketPrefix: d.Get("bucket_prefix").(string), - MinioACL: d.Get("acl").(string), - MinioForceDestroy: d.Get("force_destroy").(bool), - ObjectLockingEnabled: d.Get("object_locking").(bool), + MinioBucket: getOptionalField(d, "bucket", "").(string), + MinioBucketPrefix: getOptionalField(d, "bucket_prefix", "").(string), + MinioACL: getOptionalField(d, "acl", "private").(string), + MinioForceDestroy: getOptionalField(d, "force_destroy", false).(bool), + ObjectLockingEnabled: getOptionalField(d, "object_locking", false).(bool), } } -// BucketPolicyConfig creates config for managing minio bucket policies +// BucketPolicyConfig creates configuration for managing MinIO bucket policies. +// It sets up the basic policy configuration for a bucket. func BucketPolicyConfig(d *schema.ResourceData, meta interface{}) *S3MinioBucketPolicy { m := meta.(*S3MinioClient) return &S3MinioBucketPolicy{ MinioClient: m.S3Client, - MinioBucket: d.Get("bucket").(string), - MinioBucketPolicy: d.Get("policy").(string), + MinioBucket: getOptionalField(d, "bucket", "").(string), + MinioBucketPolicy: getOptionalField(d, "policy", "").(string), } } -// BucketVersioningConfig creates config for managing minio bucket versioning +// BucketVersioningConfig creates configuration for managing MinIO bucket versioning. +// It handles versioning configuration including excluded prefixes and folders. func BucketVersioningConfig(d *schema.ResourceData, meta interface{}) *S3MinioBucketVersioning { m := meta.(*S3MinioClient) @@ -41,38 +64,44 @@ func BucketVersioningConfig(d *schema.ResourceData, meta interface{}) *S3MinioBu return &S3MinioBucketVersioning{ MinioClient: m.S3Client, - MinioBucket: d.Get("bucket").(string), + MinioBucket: getOptionalField(d, "bucket", "").(string), VersioningConfiguration: versioningConfig, } } -// BucketVersioningConfig creates config for managing minio bucket versioning +// BucketReplicationConfig creates configuration for managing MinIO bucket replication. +// It sets up replication rules between buckets. func BucketReplicationConfig(d *schema.ResourceData, meta interface{}) (*S3MinioBucketReplication, diag.Diagnostics) { m := meta.(*S3MinioClient) replicationRules, diags := getBucketReplicationConfig(d.Get("rule").([]interface{})) + if diags.HasError() { + return nil, diags + } return &S3MinioBucketReplication{ MinioClient: m.S3Client, MinioAdmin: m.S3Admin, - MinioBucket: d.Get("bucket").(string), + MinioBucket: getOptionalField(d, "bucket", "").(string), ReplicationRules: replicationRules, - }, diags + }, nil } -// BucketNotificationConfig creates config for managing minio bucket notifications +// BucketNotificationConfig creates configuration for managing MinIO bucket notifications. +// It sets up event notifications for bucket operations. func BucketNotificationConfig(d *schema.ResourceData, meta interface{}) *S3MinioBucketNotification { m := meta.(*S3MinioClient) config := getNotificationConfiguration(d) return &S3MinioBucketNotification{ MinioClient: m.S3Client, - MinioBucket: d.Get("bucket").(string), + MinioBucket: getOptionalField(d, "bucket", "").(string), Configuration: &config, } } -// BucketServerSideEncryptionConfig creates config for managing minio bucket server side encryption +// BucketServerSideEncryptionConfig creates configuration for managing MinIO bucket server-side encryption. +// It handles encryption settings for bucket objects. func BucketServerSideEncryptionConfig(d *schema.ResourceData, meta interface{}) *S3MinioBucketServerSideEncryption { m := meta.(*S3MinioClient) @@ -80,135 +109,148 @@ func BucketServerSideEncryptionConfig(d *schema.ResourceData, meta interface{}) return &S3MinioBucketServerSideEncryption{ MinioClient: m.S3Client, - MinioBucket: d.Get("bucket").(string), + MinioBucket: getOptionalField(d, "bucket", "").(string), Configuration: encryptionConfig, } } -// NewConfig creates a new config for minio +// NewConfig creates a new MinIO client configuration. +// It handles authentication and connection settings. func NewConfig(d *schema.ResourceData) *S3MinioConfig { - user := d.Get("minio_user").(string) + // Get user credentials with fallback to legacy access key + user := getOptionalField(d, "minio_user", "").(string) if user == "" { - user = d.Get("minio_access_key").(string) + user = getOptionalField(d, "minio_access_key", "").(string) } - password := d.Get("minio_password").(string) + // Get password with fallback to legacy secret key + password := getOptionalField(d, "minio_password", "").(string) if password == "" { - password = d.Get("minio_secret_key").(string) + password = getOptionalField(d, "minio_secret_key", "").(string) } return &S3MinioConfig{ - S3HostPort: d.Get("minio_server").(string), - S3Region: d.Get("minio_region").(string), + S3HostPort: getOptionalField(d, "minio_server", "").(string), + S3Region: getOptionalField(d, "minio_region", "us-east-1").(string), S3UserAccess: user, S3UserSecret: password, - S3SessionToken: d.Get("minio_session_token").(string), - S3APISignature: d.Get("minio_api_version").(string), - S3SSL: d.Get("minio_ssl").(bool), - S3SSLCACertFile: d.Get("minio_cacert_file").(string), - S3SSLCertFile: d.Get("minio_cert_file").(string), - S3SSLKeyFile: d.Get("minio_key_file").(string), - S3SSLSkipVerify: d.Get("minio_insecure").(bool), + S3SessionToken: getOptionalField(d, "minio_session_token", "").(string), + S3APISignature: getOptionalField(d, "minio_api_version", "v4").(string), + S3SSL: getOptionalField(d, "minio_ssl", false).(bool), + S3SSLCACertFile: getOptionalField(d, "minio_cacert_file", "").(string), + S3SSLCertFile: getOptionalField(d, "minio_cert_file", "").(string), + S3SSLKeyFile: getOptionalField(d, "minio_key_file", "").(string), + S3SSLSkipVerify: getOptionalField(d, "minio_insecure", false).(bool), } } -// ServiceAccountConfig creates new service account config +// ServiceAccountConfig creates configuration for MinIO service accounts. +// It handles service account creation and management. func ServiceAccountConfig(d *schema.ResourceData, meta interface{}) *S3MinioServiceAccountConfig { m := meta.(*S3MinioClient) return &S3MinioServiceAccountConfig{ MinioAdmin: m.S3Admin, - MinioAccessKey: d.Get("access_key").(string), - MinioTargetUser: d.Get("target_user").(string), - MinioDisableUser: d.Get("disable_user").(bool), - MinioUpdateKey: d.Get("update_secret").(bool), - MinioSAPolicy: d.Get("policy").(string), - MinioName: d.Get("name").(string), - MinioDescription: d.Get("description").(string), - MinioExpiration: d.Get("expiration").(string), + MinioAccessKey: getOptionalField(d, "access_key", "").(string), + MinioTargetUser: getOptionalField(d, "target_user", "").(string), + MinioDisableUser: getOptionalField(d, "disable_user", false).(bool), + MinioUpdateKey: getOptionalField(d, "update_secret", false).(bool), + MinioSAPolicy: getOptionalField(d, "policy", "").(string), + MinioName: getOptionalField(d, "name", "").(string), + MinioDescription: getOptionalField(d, "description", "").(string), + MinioExpiration: getOptionalField(d, "expiration", "").(string), } } -// IAMUserConfig creates new user config +// IAMUserConfig creates configuration for MinIO IAM users. +// It handles user creation and management in the IAM system. func IAMUserConfig(d *schema.ResourceData, meta interface{}) *S3MinioIAMUserConfig { m := meta.(*S3MinioClient) return &S3MinioIAMUserConfig{ MinioAdmin: m.S3Admin, - MinioIAMName: d.Get("name").(string), - MinioSecret: d.Get("secret").(string), - MinioDisableUser: d.Get("disable_user").(bool), - MinioUpdateKey: d.Get("update_secret").(bool), - MinioForceDestroy: d.Get("force_destroy").(bool), + MinioIAMName: getOptionalField(d, "name", "").(string), + MinioSecret: getOptionalField(d, "secret", "").(string), + MinioDisableUser: getOptionalField(d, "disable_user", false).(bool), + MinioUpdateKey: getOptionalField(d, "update_secret", false).(bool), + MinioForceDestroy: getOptionalField(d, "force_destroy", false).(bool), } } -// IAMGroupConfig creates new group config +// IAMGroupConfig creates configuration for MinIO IAM groups. +// It handles group creation and management. func IAMGroupConfig(d *schema.ResourceData, meta interface{}) *S3MinioIAMGroupConfig { m := meta.(*S3MinioClient) return &S3MinioIAMGroupConfig{ MinioAdmin: m.S3Admin, - MinioIAMName: d.Get("name").(string), - MinioDisableGroup: d.Get("disable_group").(bool), - MinioForceDestroy: d.Get("force_destroy").(bool), + MinioIAMName: getOptionalField(d, "name", "").(string), + MinioDisableGroup: getOptionalField(d, "disable_group", false).(bool), + MinioForceDestroy: getOptionalField(d, "force_destroy", false).(bool), } } -// IAMGroupAttachmentConfig creates new membership config for a single user +// IAMGroupAttachmentConfig creates configuration for MinIO IAM group attachments. +// It handles attaching a single user to a group. func IAMGroupAttachmentConfig(d *schema.ResourceData, meta interface{}) *S3MinioIAMGroupAttachmentConfig { m := meta.(*S3MinioClient) return &S3MinioIAMGroupAttachmentConfig{ MinioAdmin: m.S3Admin, - MinioIAMUser: d.Get("user_name").(string), - MinioIAMGroup: d.Get("group_name").(string), + MinioIAMUser: getOptionalField(d, "user_name", "").(string), + MinioIAMGroup: getOptionalField(d, "group_name", "").(string), } } -// IAMGroupMembersipConfig creates new membership config -func IAMGroupMembersipConfig(d *schema.ResourceData, meta interface{}) *S3MinioIAMGroupMembershipConfig { +// IAMGroupMembershipConfig creates configuration for MinIO IAM group memberships. +// It handles attaching multiple users to a group. +func IAMGroupMembershipConfig(d *schema.ResourceData, meta interface{}) *S3MinioIAMGroupMembershipConfig { m := meta.(*S3MinioClient) + users := getStringList(d.Get("users").(*schema.Set).List()) + return &S3MinioIAMGroupMembershipConfig{ MinioAdmin: m.S3Admin, - MinioIAMName: d.Get("name").(string), - MinioIAMUsers: getStringList(d.Get("users").(*schema.Set).List()), - MinioIAMGroup: d.Get("group").(string), + MinioIAMName: getOptionalField(d, "name", "").(string), + MinioIAMUsers: users, + MinioIAMGroup: getOptionalField(d, "group", "").(string), } } -// IAMPolicyConfig creates new policy config +// IAMPolicyConfig creates configuration for MinIO IAM policies. +// It handles policy creation and management. func IAMPolicyConfig(d *schema.ResourceData, meta interface{}) *S3MinioIAMPolicyConfig { m := meta.(*S3MinioClient) return &S3MinioIAMPolicyConfig{ MinioAdmin: m.S3Admin, - MinioIAMName: d.Get("name").(string), - MinioIAMNamePrefix: d.Get("name_prefix").(string), - MinioIAMPolicy: d.Get("policy").(string), + MinioIAMName: getOptionalField(d, "name", "").(string), + MinioIAMNamePrefix: getOptionalField(d, "name_prefix", "").(string), + MinioIAMPolicy: getOptionalField(d, "policy", "").(string), } } -// IAMGroupPolicyConfig creates new group policy config +// IAMGroupPolicyConfig creates configuration for MinIO IAM group policies. +// It handles attaching policies to groups. func IAMGroupPolicyConfig(d *schema.ResourceData, meta interface{}) *S3MinioIAMGroupPolicyConfig { m := meta.(*S3MinioClient) return &S3MinioIAMGroupPolicyConfig{ MinioAdmin: m.S3Admin, - MinioIAMName: d.Get("name").(string), - MinioIAMNamePrefix: d.Get("name_prefix").(string), - MinioIAMPolicy: d.Get("policy").(string), - MinioIAMGroup: d.Get("group").(string), + MinioIAMName: getOptionalField(d, "name", "").(string), + MinioIAMNamePrefix: getOptionalField(d, "name_prefix", "").(string), + MinioIAMPolicy: getOptionalField(d, "policy", "").(string), + MinioIAMGroup: getOptionalField(d, "group", "").(string), } } -// KMSKeyConfig creates new service account config +// KMSKeyConfig creates configuration for MinIO KMS keys. +// It handles key management system configuration. func KMSKeyConfig(d *schema.ResourceData, meta interface{}) *S3MinioKMSKeyConfig { m := meta.(*S3MinioClient) return &S3MinioKMSKeyConfig{ MinioAdmin: m.S3Admin, - MinioKMSKeyID: d.Get("key_id").(string), + MinioKMSKeyID: getOptionalField(d, "key_id", "").(string), } } diff --git a/minio/error.go b/minio/error.go index 8c4d924c..57f6f988 100644 --- a/minio/error.go +++ b/minio/error.go @@ -7,27 +7,83 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" ) -// NewResourceError creates a new error with the given msg argument. +const ( + // ErrorSeverityFatal indicates a fatal error that prevents further execution + ErrorSeverityFatal = "[FATAL]" +) + +// ResourceError represents an error that occurred while managing a MinIO resource +type ResourceError struct { + Message string + Resource string + Err error +} + +// Error implements the error interface for ResourceError +func (e *ResourceError) Error() string { + if e.Err != nil { + return fmt.Sprintf("%s %s (%s): %v", ErrorSeverityFatal, e.Message, e.Resource, e.Err) + } + return fmt.Sprintf("%s %s (%s)", ErrorSeverityFatal, e.Message, e.Resource) +} + +// NewResourceError creates a diagnostic error for a MinIO resource operation. +// It handles different error types and formats them consistently: +// - diag.Diagnostics: appends a new diagnostic +// - error: creates a new diagnostic with error details +// - other: creates a new diagnostic with string representation +// +// Parameters: +// - msg: A descriptive message about what operation failed +// - resource: The name or identifier of the resource that failed +// - err: The underlying error (can be diag.Diagnostics, error, or other type) +// +// Returns: +// - diag.Diagnostics containing the formatted error func NewResourceError(msg string, resource string, err interface{}) diag.Diagnostics { - switch err := err.(type) { + // Create a ResourceError for consistent error formatting + resErr := &ResourceError{ + Message: msg, + Resource: resource, + } + + switch e := err.(type) { case diag.Diagnostics: - return append(err, diag.Diagnostic{ + return append(e, diag.Diagnostic{ Severity: diag.Error, - Summary: fmt.Sprintf("[FATAL] %s (%s)", msg, resource), + Summary: resErr.Error(), }) case error: - return diag.Errorf("[FATAL] %s (%s): %s", msg, resource, err) + resErr.Err = e + return diag.Errorf("%s", resErr.Error()) default: - return diag.Errorf("[FATAL] %s (%s): %v", msg, resource, err) + return diag.Errorf("%s %s (%s): %v", ErrorSeverityFatal, msg, resource, err) } } -// NewResourceErrorStr creates a new error with the given msg argument. +// NewResourceErrorStr creates a string representation of a resource error. +// This is useful when you need to return a string error message instead of diagnostics. +// +// Parameters: +// - msg: A descriptive message about what operation failed +// - resource: The name or identifier of the resource that failed +// - err: The underlying error (can be diag.Diagnostics, error, or other type) +// +// Returns: +// - A string containing all error messages joined with commas func NewResourceErrorStr(msg string, resource string, err interface{}) string { diags := NewResourceError(msg, resource, err) - strs := make([]string, len(diags)) + if len(diags) == 0 { + return "" + } + + // Create a slice with the correct initial capacity + strs := make([]string, 0, len(diags)) for _, d := range diags { - strs = append(strs, d.Summary) + if d.Summary != "" { + strs = append(strs, d.Summary) + } } + return strings.Join(strs, ", ") } diff --git a/minio/new_client.go b/minio/new_client.go index 87f2301b..1ee717c8 100644 --- a/minio/new_client.go +++ b/minio/new_client.go @@ -14,48 +14,48 @@ import ( "github.com/minio/minio-go/v7/pkg/credentials" ) -// NewClient returns a new minio client -func (config *S3MinioConfig) NewClient() (client interface{}, err error) { - - var minioClient *minio.Client - var minioCredentials *credentials.Credentials +const ( + // MinTLSVersion is the minimum TLS version supported + MinTLSVersion = tls.VersionTLS12 +) +// NewClient creates and configures both S3 and admin clients for MinIO +// It handles the setup of credentials, SSL/TLS configuration, and custom transport options +func (config *S3MinioConfig) NewClient() (interface{}, error) { + // Set up custom transport with SSL/TLS configuration tr, err := config.customTransport() if err != nil { - log.Println("[FATAL] Error configuring S3 client transport.") - return nil, err + return nil, fmt.Errorf("failed to configure transport: %w", err) } - if config.S3APISignature == "v2" { + // Initialize credentials based on API signature version + var minioCredentials *credentials.Credentials + switch config.S3APISignature { + case "v2": minioCredentials = credentials.NewStaticV2(config.S3UserAccess, config.S3UserSecret, config.S3SessionToken) - minioClient, err = minio.New(config.S3HostPort, &minio.Options{ - Creds: minioCredentials, - Secure: config.S3SSL, - Transport: tr, - }) - } else if config.S3APISignature == "v4" { + case "v4": minioCredentials = credentials.NewStaticV4(config.S3UserAccess, config.S3UserSecret, config.S3SessionToken) - minioClient, err = minio.New(config.S3HostPort, &minio.Options{ - Creds: minioCredentials, - Secure: config.S3SSL, - Transport: tr, - }) - } else { - return nil, fmt.Errorf("unknown S3 API signature: %s, must be v2 or v4", config.S3APISignature) + default: + return nil, fmt.Errorf("unsupported S3 API signature version %q: must be v2 or v4", config.S3APISignature) } + + // Initialize S3 client + minioClient, err := minio.New(config.S3HostPort, &minio.Options{ + Creds: minioCredentials, + Secure: config.S3SSL, + Transport: tr, + }) if err != nil { - log.Println("[FATAL] Error building client for S3 server.") - return nil, err + return nil, fmt.Errorf("failed to create S3 client: %w", err) } + // Initialize admin client minioAdmin, err := madmin.NewWithOptions(config.S3HostPort, &madmin.Options{ Creds: minioCredentials, Secure: config.S3SSL, }) - //minioAdmin.TraceOn(nil) if err != nil { - log.Println("[FATAL] Error building admin client for S3 server.") - return nil, err + return nil, fmt.Errorf("failed to create admin client: %w", err) } minioAdmin.SetCustomTransport(tr) @@ -67,70 +67,92 @@ func (config *S3MinioConfig) NewClient() (client interface{}, err error) { }, nil } -func isValidCertificate(c []byte) bool { - p, _ := pem.Decode(c) - if p == nil { +// isValidCertificate checks if the provided bytes represent a valid x509 certificate in PEM format +func isValidCertificate(certBytes []byte) bool { + block, _ := pem.Decode(certBytes) + if block == nil { return false } - _, err := x509.ParseCertificates(p.Bytes) + _, err := x509.ParseCertificates(block.Bytes) return err == nil } +// customTransport creates and configures an HTTP transport with SSL/TLS settings +// It handles CA certificates, client certificates, and verification settings func (config *S3MinioConfig) customTransport() (*http.Transport, error) { - + // If SSL is disabled, return default transport if !config.S3SSL { return minio.DefaultTransport(config.S3SSL) } + // Initialize TLS config with minimum version tlsConfig := &tls.Config{ - // Can't use SSLv3 because of POODLE and BEAST - // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher - // Can't use TLSv1.1 because of RC4 cipher usage - MinVersion: tls.VersionTLS12, + MinVersion: MinTLSVersion, // Minimum TLS 1.2 for security } + // Get default transport tr, err := minio.DefaultTransport(config.S3SSL) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create default transport: %w", err) } + // Configure CA certificate if provided if config.S3SSLCACertFile != "" { - minioCACert, err := os.ReadFile(config.S3SSLCACertFile) - if err != nil { + if err := config.configureCACert(tlsConfig); err != nil { return nil, err } - - if !isValidCertificate(minioCACert) { - return nil, fmt.Errorf("minio CA Cert is not a valid x509 certificate") - } - - rootCAs, _ := x509.SystemCertPool() - if rootCAs == nil { - // In some systems (like Windows) system cert pool is - // not supported or no certificates are present on the - // system - so we create a new cert pool. - rootCAs = x509.NewCertPool() - } - rootCAs.AppendCertsFromPEM(minioCACert) - tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert - tlsConfig.RootCAs = rootCAs } + // Configure client certificates if both cert and key are provided if config.S3SSLCertFile != "" && config.S3SSLKeyFile != "" { - minioPair, err := tls.LoadX509KeyPair(config.S3SSLCertFile, config.S3SSLKeyFile) - if err != nil { + if err := config.configureClientCert(tlsConfig); err != nil { return nil, err } - tlsConfig.Certificates = []tls.Certificate{minioPair} } - if config.S3SSLSkipVerify { - tlsConfig.InsecureSkipVerify = true - } + // Configure SSL verification + tlsConfig.InsecureSkipVerify = config.S3SSLSkipVerify + // Set TLS config on transport tr.TLSClientConfig = tlsConfig - log.Printf("[DEBUG] S3 SSL client initialized") - + log.Printf("[DEBUG] MinIO SSL client transport configured successfully") return tr, nil } + +// configureCACert loads and configures the CA certificate for TLS verification +func (config *S3MinioConfig) configureCACert(tlsConfig *tls.Config) error { + caCert, err := os.ReadFile(config.S3SSLCACertFile) + if err != nil { + return fmt.Errorf("failed to read CA certificate file: %w", err) + } + + if !isValidCertificate(caCert) { + return fmt.Errorf("invalid CA certificate: not a valid x509 certificate") + } + + rootCAs, _ := x509.SystemCertPool() + if rootCAs == nil { + // Some systems don't support system cert pool + rootCAs = x509.NewCertPool() + } + + if !rootCAs.AppendCertsFromPEM(caCert) { + return fmt.Errorf("failed to append CA certificate to cert pool") + } + + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.RootCAs = rootCAs + return nil +} + +// configureClientCert loads and configures client certificates for mutual TLS +func (config *S3MinioConfig) configureClientCert(tlsConfig *tls.Config) error { + cert, err := tls.LoadX509KeyPair(config.S3SSLCertFile, config.S3SSLKeyFile) + if err != nil { + return fmt.Errorf("failed to load client certificate and key: %w", err) + } + + tlsConfig.Certificates = []tls.Certificate{cert} + return nil +} diff --git a/minio/provider.go b/minio/provider.go index 1ad764d3..0d066d2f 100644 --- a/minio/provider.go +++ b/minio/provider.go @@ -2,122 +2,139 @@ package minio import ( "context" + "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -// Provider creates a new provider +// Provider creates and returns a new Terraform provider for MinIO. +// It initializes all the necessary resources, data sources, and configuration +// options for interacting with a MinIO server. func Provider() *schema.Provider { return newProvider() } -func newProvider(envvarPrefixed ...string) *schema.Provider { - envVarPrefix := "" - if len(envvarPrefixed) != 0 { - envVarPrefix = envvarPrefixed[0] +// newProvider creates a new MinIO provider with the given environment variable prefix. +// If no prefix is provided, it uses default environment variable names. +func newProvider(envVarPrefix ...string) *schema.Provider { + prefix := "" + if len(envVarPrefix) > 0 { + prefix = envVarPrefix[0] } - return &schema.Provider{ + + p := &schema.Provider{ Schema: map[string]*schema.Schema{ "minio_server": { Type: schema.TypeString, Required: true, - Description: "Minio Host and Port", + Description: "MinIO server endpoint in the format host:port", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_ENDPOINT", + prefix + "MINIO_ENDPOINT", }, nil), }, "minio_region": { Type: schema.TypeString, Optional: true, Default: "us-east-1", - Description: "Minio Region (default: us-east-1)", + Description: "MinIO server region (default: us-east-1)", }, - "minio_access_key": { + // Authentication credentials + "minio_user": { Type: schema.TypeString, Optional: true, - Description: "Minio Access Key", + Description: "MinIO user (or access key) for authentication", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_ACCESS_KEY", + prefix + "MINIO_USER", }, nil), - Deprecated: "use minio_user instead", + ConflictsWith: []string{"minio_access_key"}, }, - "minio_secret_key": { + "minio_password": { Type: schema.TypeString, Optional: true, - Description: "Minio Secret Key", + Sensitive: true, + Description: "MinIO password (or secret key) for authentication", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_SECRET_KEY", + prefix + "MINIO_PASSWORD", }, nil), - Deprecated: "use minio_password instead", + ConflictsWith: []string{"minio_secret_key"}, }, - "minio_user": { + "minio_access_key": { Type: schema.TypeString, Optional: true, - Description: "Minio User (or access key)", + Description: "MinIO access key (deprecated: use minio_user instead)", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_USER", + prefix + "MINIO_ACCESS_KEY", }, nil), - ConflictsWith: []string{"minio_access_key"}, + Deprecated: "use minio_user instead", }, - "minio_password": { + "minio_secret_key": { Type: schema.TypeString, Optional: true, - Description: "Minio Password (or secret key)", + Sensitive: true, + Description: "MinIO secret key (deprecated: use minio_password instead)", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_PASSWORD", + prefix + "MINIO_SECRET_KEY", }, nil), - ConflictsWith: []string{"minio_secret_key"}, + Deprecated: "use minio_password instead", }, "minio_session_token": { Type: schema.TypeString, Optional: true, - Description: "Minio Session Token", + Sensitive: true, + Description: "MinIO session token for temporary credentials", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_SESSION_TOKEN", + prefix + "MINIO_SESSION_TOKEN", }, ""), }, + // API and Security configuration "minio_api_version": { - Type: schema.TypeString, - Optional: true, - Default: "v4", - Description: "Minio API Version (type: string, options: v2 or v4, default: v4)", + Type: schema.TypeString, + Optional: true, + Default: "v4", + Description: "MinIO API Version (v2 or v4)", + ValidateFunc: validateAPIVersion, }, "minio_ssl": { Type: schema.TypeBool, Optional: true, - Description: "Minio SSL enabled (default: false)", + Description: "Enable SSL/TLS for MinIO connection", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_ENABLE_HTTPS", - }, nil), + prefix + "MINIO_ENABLE_HTTPS", + }, false), }, "minio_insecure": { Type: schema.TypeBool, Optional: true, - Description: "Disable SSL certificate verification (default: false)", + Description: "Skip SSL certificate verification (not recommended for production)", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_INSECURE", - }, nil), + prefix + "MINIO_INSECURE", + }, false), }, + // SSL/TLS Certificate configuration "minio_cacert_file": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + Description: "Path to CA certificate file for SSL verification", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_CACERT_FILE", + prefix + "MINIO_CACERT_FILE", }, nil), }, "minio_cert_file": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + Description: "Path to client certificate file for SSL authentication", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_CERT_FILE", + prefix + "MINIO_CERT_FILE", }, nil), }, "minio_key_file": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + Sensitive: true, + Description: "Path to client private key file for SSL authentication", DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - envVarPrefix + "MINIO_KEY_FILE", + prefix + "MINIO_KEY_FILE", }, nil), }, }, @@ -128,6 +145,7 @@ func newProvider(envvarPrefixed ...string) *schema.Provider { }, ResourcesMap: map[string]*schema.Resource{ + // S3 Bucket Operations "minio_s3_bucket": resourceMinioBucket(), "minio_s3_bucket_policy": resourceMinioBucketPolicy(), "minio_s3_bucket_versioning": resourceMinioBucketVersioning(), @@ -136,31 +154,49 @@ func newProvider(envvarPrefixed ...string) *schema.Provider { "minio_s3_bucket_notification": resourceMinioBucketNotification(), "minio_s3_bucket_server_side_encryption": resourceMinioBucketServerSideEncryption(), "minio_s3_object": resourceMinioObject(), - "minio_iam_group": resourceMinioIAMGroup(), - "minio_iam_group_membership": resourceMinioIAMGroupMembership(), - "minio_iam_user": resourceMinioIAMUser(), - "minio_iam_service_account": resourceMinioServiceAccount(), - "minio_iam_group_policy": resourceMinioIAMGroupPolicy(), - "minio_iam_policy": resourceMinioIAMPolicy(), - "minio_iam_user_policy_attachment": resourceMinioIAMUserPolicyAttachment(), - "minio_iam_group_policy_attachment": resourceMinioIAMGroupPolicyAttachment(), - "minio_iam_group_user_attachment": resourceMinioIAMGroupUserAttachment(), + + // IAM Operations + "minio_iam_group": resourceMinioIAMGroup(), + "minio_iam_group_membership": resourceMinioIAMGroupMembership(), + "minio_iam_user": resourceMinioIAMUser(), + "minio_iam_service_account": resourceMinioServiceAccount(), + "minio_iam_group_policy": resourceMinioIAMGroupPolicy(), + "minio_iam_policy": resourceMinioIAMPolicy(), + "minio_iam_user_policy_attachment": resourceMinioIAMUserPolicyAttachment(), + "minio_iam_group_policy_attachment": resourceMinioIAMGroupPolicyAttachment(), + "minio_iam_group_user_attachment": resourceMinioIAMGroupUserAttachment(), + + // LDAP Operations "minio_iam_ldap_group_policy_attachment": resourceMinioIAMLDAPGroupPolicyAttachment(), "minio_iam_ldap_user_policy_attachment": resourceMinioIAMLDAPUserPolicyAttachment(), - "minio_ilm_policy": resourceMinioILMPolicy(), - "minio_kms_key": resourceMinioKMSKey(), - "minio_ilm_tier": resourceMinioILMTier(), + + // ILM and KMS Operations + "minio_ilm_policy": resourceMinioILMPolicy(), + "minio_ilm_tier": resourceMinioILMTier(), + "minio_kms_key": resourceMinioKMSKey(), }, ConfigureContextFunc: providerConfigure, } + + return p +} + +// validateAPIVersion ensures that the API version is either "v2" or "v4" +func validateAPIVersion(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "v2" && value != "v4" { + errors = append(errors, fmt.Errorf("%q must be either 'v2' or 'v4', got: %s", k, value)) + } + return } +// providerConfigure configures the MinIO client using the provided configuration func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { minioConfig := NewConfig(d) client, err := minioConfig.NewClient() if err != nil { - return nil, NewResourceError("client creation failed", "client", err) + return nil, NewResourceError("Failed to create MinIO client", "client_creation", err) } return client, nil diff --git a/minio/resource_minio_iam_group_membership.go b/minio/resource_minio_iam_group_membership.go index 7411a5ae..7fdb1902 100644 --- a/minio/resource_minio_iam_group_membership.go +++ b/minio/resource_minio_iam_group_membership.go @@ -48,7 +48,7 @@ func resourceMinioIAMGroupMembership() *schema.Resource { } func minioCreateGroupMembership(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - iamGroupMembershipConfig := IAMGroupMembersipConfig(d, meta) + iamGroupMembershipConfig := IAMGroupMembershipConfig(d, meta) groupAddRemove := madmin.GroupAddRemove{ Group: iamGroupMembershipConfig.MinioIAMGroup, @@ -67,7 +67,7 @@ func minioCreateGroupMembership(ctx context.Context, d *schema.ResourceData, met } func minioUpdateGroupMembership(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - iamGroupMembershipConfig := IAMGroupMembersipConfig(d, meta) + iamGroupMembershipConfig := IAMGroupMembershipConfig(d, meta) if d.HasChange("users") { on, nn := d.GetChange("users") @@ -100,7 +100,7 @@ func minioUpdateGroupMembership(ctx context.Context, d *schema.ResourceData, met } func minioReadGroupMembership(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - iamGroupMembershipConfig := IAMGroupMembersipConfig(d, meta) + iamGroupMembershipConfig := IAMGroupMembershipConfig(d, meta) groupDesc, err := iamGroupMembershipConfig.MinioAdmin.GetGroupDescription(ctx, iamGroupMembershipConfig.MinioIAMGroup) if err != nil { @@ -127,7 +127,7 @@ func minioReadGroupMembership(ctx context.Context, d *schema.ResourceData, meta } func minioDeleteGroupMembership(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - iamGroupMembershipConfig := IAMGroupMembersipConfig(d, meta) + iamGroupMembershipConfig := IAMGroupMembershipConfig(d, meta) groupAddRemove := madmin.GroupAddRemove{ Group: iamGroupMembershipConfig.MinioIAMGroup,