PowerShell DSC for Linux: Resources

New version of PowerShell DSC for Linux doubled the number of resources available. Initial batch in CTP covered basics: nxUser to create and manage users, nxGroup to define groups and manage group members, nxFile to create and modify files, nxService to start, stop, enable, and disable daemons, and nxScript to cover anything that couldn’t be done with other resources. We covered these resources in the previous series and they haven’t changed much since.
New resources cover installation of packages (nxPackage), injecting single line into existing file (nxFileLine), configuring environment variables (nxEnvironment), working with archived data (nxArchive) and setting up authorized SSH keys (nxSShAuthorizedKeys). In this part of the series we will focus on these newly-added resources.

nxPackage

We will start with nxPackage. For me personally that is the most important resource from those added in this release. I created composite resource for previous version to cover this gap. Unlike my composite resource, the one that shipped covers several package managers. It has support for local installation of packages. You just have to specify FilePath to package file. It understands concepts of package groups. You can specify Arguments for installation and define what is expected ReturnCode. I’m a CentOS user. So, I’ve tested it with yum. It worked fine for any scenarios that I’ve tried. For very basic scenarios when you specify absolute minimum information, the only required property is the Name:

configuration packages {
    Import-DscResource -ModuleName nx
    node LinuxNode {
        nxPackage apache {
            Name = 'httpd'
        }
    }
}

If you don’t specify package manager the resource will try to find one for you. That means that if you are not sure or have a mixed environment with distributions that resemble both Debian and RedHat, you can use one configuration and it will “just work”. The part of code that does the magic is using which command:

def GetPackageManager():
    ret = None
    # choose default - almost surely one will match.
    for b in ('apt-get', 'zypper', 'yum'):
        code, out = RunGetOutput('which ' + b, False, False)
        if code is 0:
            ret = b
            if ret == 'apt-get':
                ret = 'apt'
            break
    return ret

In my opinion that is very elegant solution to the problem of package management diversity between Linux distributions.
If we don’t want to depend on the resource to find package manager for us or we want to be sure that specific manager is used, we need to specify it in our configuration. If given manager is not present on remote system, we should get an error but that is unfortunately not very verbose. To find actual cause, we would have to use my favorite (and from my experience–the most effective) debugging technique, run omiserver interactively:

CalledProcessError.  Error Code is 127
CalledProcessError.  Command string was dpkg-query -W -f='${Description}|${Maintainer}|'Unknown'|${Installed-Size}|${Version}|${Status}
' httpd
CalledProcessError.  Command result was /bin/sh: dpkg-query: command not found
check installed:/bin/sh: dpkg-query: command not found

CalledProcessError.  Error Code is 127
CalledProcessError.  Command string was apt-get  install  --allow-unauthenticated --yes  httpd
CalledProcessError.  Command result was /bin/sh: apt-get: command not found
Failed to Install httpd output for command was: /bin/sh: apt-get: command not found

My system doesn’t have apt-get on it, so error message is expected.
If we want to remove packages, we can do it by specifying value Absent to Ensure property.

nxFileLine

Another interesting and potentially useful resource is nxFileLine. Unlike nxFile, this one is suitable for any component that have just one configuration file without option to read parts of the configuration from other files stored on disk. In such a case, replacing whole file may not be the best option. What we need is an option to remove certain lines or add lines that we need. Former can be achieved with a DoesNotContainPattern property which accepts regular expressions. Latter is done with ContainsLine property that specifies the line that needs to be appended. You have to be aware that there is no correlation between the two: new line is always added at the end of a file, so if position in the file is important; nxFileLine won’t help you. Example configuration that makes sure that sshd is not trying to resolve hosts (handy in lab environment) and removes any line that is just a comment:

configuration Lines {
    Import-DscResource -ModuleName nx
    node LinuxNode {
        nxFileLine sshd {
            FilePath = '/etc/ssh/sshd_config'
            DoesNotContainPattern = '^#.*?$|UseDNS yes'
            ContainsLine = 'UseDNS no'
        }
    }
}

nxEnvironment

Another common need on Linux system is configuration of environment variables. Their purpose and behavior is similar to the one we observe on Windows: most of variables are product-specific and are used only by certain applications. There is one environment variable that stands out: PATH. The only difference between both systems is separator: Windows is using semicolon where Linux is using colon. One of the resources added in this release (nxEnvironment) is designed to configure these variables, including the PATH. Example configuration adds OMI_HOME variable and adds folder with OMI binaries to the PATH:

configuration Environment {
    Import-DscResource -ModuleName nx
    node LinuxNode {
        nxEnvironment Normal {
            Name = 'OMI_HOME'
            Value = '/opt/omi'
        }
        nxEnvironment Path {
            Name = 'WhoCares?'
            Path = $true
            Value = '/opt/omi/bin'
        }
    }
}

This configuration highlights two differences between “normal” variables and the PATH variable. Former use name to specify the name of variable, for PATH it’s still a key value, but used only to differentiate between several items added to the path. To tell the resource that it’s a path that we configure we need to provide value $true to Path parameter. Another difference is a way both types are defined on Linux box. PATH is configured in /etc/profile.d/DSCEnvironment.sh by appending each item to existing PATH with colon as a separator. Any other variable definition is added to /etc/environment that is dot-sourced in DSCEnvironment.sh. The only problem I’ve found so far with this resource is related to Ensure property. Specifying Present works fine, but Absent don’t seem to change anything if variable is defined in another script under /etc/profile.d.

nxArchive

Another resource, nxArchive, should help us with using archived files. All you need to do is specify path to archive and destination for the files. This resource have very similar properties as Archive available for Windows. The difference is checksum usage: we can choose between md5, mtime, and ctime. Another difference is lack of credential support and validate property. Example configuration would use archive /tmp/source.tgz and create /tmp/target folder using it content:

configuration Archive {
    Import-DscResource -ModuleName nx
    node 192.168.7.204 {
        nxArchive tmp {
            SourcePath = '/tmp/source.tgz'
            DestinationPath = '/tmp/target'
            Checksum = 'md5'
        }
    }
} 

nxSshAuthorizedKeys

Last resource added in this release should address issue of managing SSH keys for a given user. With nxSshAuthorizedKeys resource we can deliver keys to any number of nodes and be sure that our private key will work seamlessly with all of them. It will take care of proper file structure and permissions on both .ssh folder and authorized_keys file:

[root@PSMag ~]# ls /home/bielawb/.ssh/ -las
total 4
0 drwx------. 2 bielawb bielawb  28 Jun 14 01:29 .
0 drwx------. 3 bielawb bielawb  90 Jun 14 01:29 ..
4 -rwx------. 1 bielawb bielawb 424 Jun 14 01:29 authorized_keys

Something that might require change is rights for authorized_keys files: x is not necessary. It’s not a big issue: keys will work just fine with either.
There are two odd things about this resource: first of all, KeyComment is marked as a key in the schema. Probably because there was no natural candidate that could be used: same key value may need to be applied to several users, same user can have multiple keys. Other one is more important: even though schema doesn’t define UserName as required, it is required by the resource. This means that instead of getting errors at compilation phase, we will get them at implementation phase. Example configuration (with actual key removed):

configuration SshKeys {
    Import-DscResource -ModuleName nx
    node LinuxNode {
        nxSshAuthorizedKeys bielawb {
            Key = 'ssh-rsa key description'
            KeyComment = 'How will that work?'
            UserName = 'bielawb'
        }
    }
}

Because there isn’t any strict link between resource key and actual data stored in the file we can easily break existing one by adding single line between comment and actual key data, or by removing comment from the file. If that happens DSC will duplicate the line with a key. It’s not a big issue, but just something that user should be aware of.
We now know what we can configure currently with PowerShell DSC for Linux. But there is also huge change in how we can achieve that. We will take a look at that in the third and final part of this series.

About the author: Bartek Bielawski

Bartek is a busy IT Admin working for an international company, Optiver. He loves PowerShell and automation. That love got him the honors of a Microsoft MVP. He shares his knowledge on his blog. You can also find him on Twitter: @bielawb.

Related Posts

%d bloggers like this: