summaryrefslogtreecommitdiff
path: root/lib/chef/resource/windows_workgroup.rb
blob: e506b51257d311280f502c05e2976c38c959a528 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#
# Author:: Derek Groh (<derekgroh@github.io>)
# Copyright:: 2018, Derek Groh
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require_relative "../resource"
require "chef-utils/dist" unless defined?(ChefUtils::Dist)

class Chef
  class Resource
    class WindowsWorkgroup < Chef::Resource
      unified_mode true

      provides :windows_workgroup

      description "Use the **windows_workgroup** resource to join or change the workgroup of a Windows host."
      introduced "14.5"
      examples <<~DOC
      **Join a workgroup**:

      ``` ruby
      windows_workgroup 'myworkgroup'
      ```

      **Join a workgroup using a specific user**:

      ``` ruby
      windows_workgroup 'myworkgroup' do
        user 'Administrator'
        password 'passw0rd'
      end
      ```
      DOC

      property :workgroup_name, String,
        description: "An optional property to set the workgroup name if it differs from the resource block's name.",
        validation_message: "The 'workgroup_name' property must not contain spaces.",
        regex: /^\S*$/, # no spaces
        name_property: true

      property :user, String,
        description: "The local administrator user to use to change the workgroup. Required if using the `password` property.",
        desired_state: false

      property :password, String,
        description: "The password for the local administrator user. Required if using the `user` property.",
        sensitive: true,
        desired_state: false

      property :reboot, Symbol,
        equal_to: %i{never request_reboot reboot_now},
        validation_message: "The reboot property accepts :immediate (reboot as soon as the resource completes), :delayed (reboot once the #{ChefUtils::Dist::Infra::PRODUCT} run completes), and :never (Don't reboot)",
        description: "Controls the system reboot behavior post workgroup joining. Reboot immediately, after the #{ChefUtils::Dist::Infra::PRODUCT} run completes, or never. Note that a reboot is necessary for changes to take effect.",
        coerce: proc { |x| clarify_reboot(x) },
        default: :immediate, desired_state: false

      # This resource historically took `:immediate` and `:delayed` as arguments to the reboot property but then
      # tried to shove that straight to the `reboot` resource which objected strenuously. We need to convert these
      # legacy actions into actual reboot actions
      #
      # @return [Symbol] chef reboot resource action
      def clarify_reboot(reboot_action)
        case reboot_action
        when :immediate
          :reboot_now
        when :delayed
          :request_reboot
        else
          reboot_action
        end
      end

      # define this again so we can default it to true. Otherwise failures print the password
      # FIXME: this should now be unnecessary with the password property itself marked sensitive?
      property :sensitive, [TrueClass, FalseClass],
        default: true, desired_state: false

      action :join do
        description "Update the workgroup."

        unless workgroup_member?
          converge_by("join workstation workgroup #{new_resource.workgroup_name}") do
            ps_run = powershell_out(join_command)
            raise "Failed to join the workgroup #{new_resource.workgroup_name}: #{ps_run.stderr}}" if ps_run.error?

            unless new_resource.reboot == :never
              reboot "Reboot to join workgroup #{new_resource.workgroup_name}" do
                action new_resource.reboot
                reason "Reboot to join workgroup #{new_resource.workgroup_name}"
              end
            end
          end
        end
      end

      action_class do
        # return [String] the appropriate PS command to joint the workgroup
        def join_command
          cmd = ""
          cmd << "$pswd = ConvertTo-SecureString \'#{new_resource.password}\' -AsPlainText -Force;" if new_resource.password
          cmd << "$credential = New-Object System.Management.Automation.PSCredential (\"#{new_resource.user}\",$pswd);" if new_resource.password
          cmd << "Add-Computer -WorkgroupName #{new_resource.workgroup_name}"
          cmd << " -Credential $credential" if new_resource.password
          cmd << " -Force"
          cmd
        end

        # @return [Boolean] is the node a member of the workgroup specified in the resource
        def workgroup_member?
          node_workgroup = powershell_out!("(Get-WmiObject -Class Win32_ComputerSystem).Workgroup")
          raise "Failed to determine if system already a member of workgroup #{new_resource.workgroup_name}" if node_workgroup.error?

          node_workgroup.stdout.downcase.strip == new_resource.workgroup_name.downcase
        end
      end
    end
  end
end