Skip to content

Commit

Permalink
Support for adding user accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
ninjarobot committed Jun 2, 2023
1 parent 5c1aeb8 commit b544241
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 11 deletions.
90 changes: 90 additions & 0 deletions FsCloudInit/Builders.fs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,91 @@ module Builders =

let aptSource = AptSourceBuilder()

/// Builder for a User.
type UserBuilder() =
member _.Yield _ = User.Default

[<CustomOperation "name">]
member _.Name(user: User, name: string) = { user with Name = name }

[<CustomOperation "expiredate">]
member _.ExpireDate(user: User, expireDate: DateTimeOffset) =
{ user with
ExpiredDate = expireDate.ToString("yyyy-MM-dd") }

[<CustomOperation "gecos">]
member _.Gecos(user: User, gecos: string) = { user with Gecos = gecos }

[<CustomOperation "groups">]
member _.Groups(user: User, groups: string seq) = { user with Groups = groups }

[<CustomOperation "homedir">]
member _.HomeDir(user: User, homedir: string) = { user with HomeDir = homedir }

[<CustomOperation "inactive">]
member _.InactiveInDays(user: User, days: int) = { user with Inactive = Nullable(days) }

[<CustomOperation "lock_passwd">]
member _.LockPasswd(user: User, lockPasswd: bool) = { user with LockPasswd = lockPasswd }

[<CustomOperation "no_create_home">]
member _.NoCreateHome(user: User, noCreateHome: bool) =
{ user with
NoCreateHome = noCreateHome }

[<CustomOperation "no_log_init">]
member _.NoLogInit(user: User, noLogInit: bool) = { user with NoLogInit = noLogInit }

[<CustomOperation "no_user_group">]
member _.NoUserGroup(user: User, noUserGroup: bool) = { user with NoUserGroup = noUserGroup }

[<CustomOperation "create_groups">]
member _.CreateGroups(user: User, createGroups: bool) =
{ user with
CreateGroups = createGroups }

[<CustomOperation "primary_group">]
member _.PrimaryGroup(user: User, primaryGroup: string) =
{ user with
PrimaryGroup = primaryGroup }

[<CustomOperation "selinux_user">]
member _.SelinuxUser(user: User, selinuxUser: string) = { user with SelinuxUser = selinuxUser }

[<CustomOperation "shell">]
member _.Shell(user: User, shell: string) = { user with Shell = shell }

[<CustomOperation "ssh_authorized_keys">]
member _.SshAuthorizedKeys(user: User, sshAuthorizedKeys: string seq) =
{ user with
SshAuthorizedKeys = Seq.append user.SshAuthorizedKeys sshAuthorizedKeys }

[<CustomOperation "ssh_import_id">]
member _.SshImportId(user: User, sshImportIds: string seq) =
{ user with
SshImportId = Seq.append user.SshImportId sshImportIds }

[<CustomOperation "ssh_import_github_id">]
member _.SshImportGitHubId(user: User, gitHubId: string) =
{ user with
SshImportId = Seq.append user.SshImportId [ $"gh:{gitHubId}" ] }

[<CustomOperation "ssh_redirect_user">]
member _.SshRedirectUser(user: User, sshRedirectUser: bool) =
{ user with
SshRedirectUser = sshRedirectUser }

[<CustomOperation "system">]
member _.System(user: User, system: bool) = { user with System = system }

[<CustomOperation "sudo">]
member _.Sudo(user: User, sudo: string) = { user with Sudo = sudo }

[<CustomOperation "uid">]
member _.Uid(user: User, uid: int) = { user with Uid = Nullable(uid) }

let user = UserBuilder()

/// Builder for a CloudConfig record.
type CloudConfigBuilder() =
member _.Yield _ = CloudConfig.Default
Expand Down Expand Up @@ -163,4 +248,9 @@ module Builders =
|> RunCmd
|> Some }

[<CustomOperation "users">]
member _.Users(cloudConfig: CloudConfig, users: User seq) =
{ cloudConfig with
Users = Seq.append cloudConfig.Users users }

let cloudConfig = CloudConfigBuilder()
86 changes: 75 additions & 11 deletions FsCloudInit/CloudConfig.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

open System
open System.Collections.Generic
open YamlDotNet.Serialization

module FileEncoding =
[<Literal>]
Expand Down Expand Up @@ -38,12 +39,22 @@ type FilePermissions =
Others = enum<PosixFilePerm> (int (((num % 10u) - (num % 1u)) / 1u)) }
| false, _ -> invalidArg "string" "Malformed permission flags."

module Sudo =
/// Defines sudo options as "ALL=(ALL) NOPASSWD:ALL"
let AllPermsNoPasswd = "ALL=(ALL) NOPASSWD:ALL"

module internal Serialization =
let serializableSeq sequence =
if Seq.isEmpty sequence then null else ResizeArray sequence

let defaultIfTrue b = if b then Unchecked.defaultof<_> else b

type WriteFile =
{ Encoding: string
Content: string
Owner: string
Path: string
[<YamlDotNet.Serialization.YamlMember(ScalarStyle = YamlDotNet.Core.ScalarStyle.SingleQuoted)>]
[<YamlMember(ScalarStyle = YamlDotNet.Core.ScalarStyle.SingleQuoted)>]
Permissions: string
Append: bool
Defer: bool }
Expand Down Expand Up @@ -90,6 +101,59 @@ type RunCmd =
match this with
| RunCmd commands -> commands |> Seq.map Seq.ofList

type User =
{ Name: string
ExpiredDate: string
Gecos: string
Groups: string seq
HomeDir: string
Inactive: Nullable<int>
LockPasswd: bool
NoCreateHome: bool
NoLogInit: bool
NoUserGroup: bool
CreateGroups: bool
PrimaryGroup: string
SelinuxUser: string
Shell: string
SshAuthorizedKeys: string seq
SshImportId: string seq
SshRedirectUser: bool
System: bool
Sudo: string
Uid: Nullable<int> }

static member Default =
{ Name = null
ExpiredDate = null
Gecos = null
Groups = []
HomeDir = null
Inactive = Nullable()
LockPasswd = true
NoCreateHome = false
NoLogInit = false
NoUserGroup = false
CreateGroups = true
PrimaryGroup = null
SelinuxUser = null
Shell = null
SshAuthorizedKeys = []
SshImportId = []
SshRedirectUser = false
System = false
Sudo = null
Uid = Nullable() }

[<YamlIgnore>]
member this.Model =
{ this with
CreateGroups = Serialization.defaultIfTrue this.CreateGroups
Groups = Serialization.serializableSeq this.Groups
LockPasswd = Serialization.defaultIfTrue this.LockPasswd
SshAuthorizedKeys = Serialization.serializableSeq this.SshAuthorizedKeys
SshImportId = Serialization.serializableSeq this.SshImportId }

type CloudConfig =
{ Apt: Apt option
FinalMessage: string option
Expand All @@ -98,6 +162,7 @@ type CloudConfig =
PackageUpgrade: bool option
PackageRebootIfRequired: bool option
RunCmd: RunCmd option
Users: User seq
WriteFiles: WriteFile seq }

static member Default =
Expand All @@ -108,21 +173,20 @@ type CloudConfig =
PackageUpgrade = None
PackageRebootIfRequired = None
RunCmd = None
Users = []
WriteFiles = [] }

member this.ConfigModel =
{| Apt = this.Apt |> Option.defaultValue Unchecked.defaultof<Apt>
FinalMessage = this.FinalMessage |> Option.toObj
Packages =
if this.Packages |> Seq.isEmpty then
null
else
this.Packages |> Seq.map (fun p -> p.Model)
Packages = this.Packages |> Seq.map (fun p -> p.Model) |> Serialization.serializableSeq
PackageUpdate = this.PackageUpdate |> Option.toNullable
PackageUpgrade = this.PackageUpgrade |> Option.toNullable
Runcmd = this.RunCmd |> Option.map (fun runCmd -> runCmd.Model) |> Option.toObj
WriteFiles =
if this.WriteFiles |> Seq.isEmpty then
null
else
this.WriteFiles |}
Users =
let users =
this.Users |> Seq.map (fun u -> box u.Model) |> Serialization.serializableSeq
if not <| isNull users then // Include the default user created by the cloud platform
users.Insert(0, "default")
users
WriteFiles = this.WriteFiles |> Serialization.serializableSeq |}
15 changes: 15 additions & 0 deletions FsCloudInitTests/BuilderTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,19 @@ let tests =
}
|> Writer.write
|> matchExpectedAt "run-command.yaml"
}
test "Create users" {
cloudConfig {
users [
user {
name "itme"
gecos "My Account"
ssh_import_github_id "mygithubusername"
groups [ "sudo" ]
sudo Sudo.AllPermsNoPasswd
}
]
}
|> Writer.write
|> matchExpectedAt "users.yaml"
} ]
10 changes: 10 additions & 0 deletions FsCloudInitTests/TestContent/users.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#cloud-config
users:
- default
- name: itme
gecos: My Account
groups:
- sudo
ssh_import_id:
- gh:mygithubusername
sudo: ALL=(ALL) NOPASSWD:ALL
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,19 @@ cloudConfig {
}
|> Writer.write
```

#### Create additional users

```f#
cloudConfig {
users [
user {
name "itme"
gecos "My Account"
ssh_import_github_id "mygithubusername"
groups [ "sudo" ]
sudo Sudo.AllPermsNoPasswd
}
]
}
```

0 comments on commit b544241

Please sign in to comment.